#24 Implement the progress meter.

已合并
jonathan 4 年前 将 4 次代码提交从 progress_meter合并至 dev
  1. +9
    -4
      ui/src/main/java/com/getbubblenow/android/activity/BaseActivityBubble.java
  2. +7
    -83
      ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java
  3. +259
    -41
      ui/src/main/java/com/getbubblenow/android/activity/MainActivity.java
  4. +1
    -0
      ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java
  5. +4
    -0
      ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java
  6. +4
    -0
      ui/src/main/java/com/getbubblenow/android/model/Network.java
  7. +78
    -0
      ui/src/main/java/com/getbubblenow/android/model/NetworkStatus.java
  8. +289
    -166
      ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java
  9. +67
    -10
      ui/src/main/java/com/getbubblenow/android/util/UserStore.java
  10. +4
    -4
      ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java
  11. +66
    -7
      ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java
  12. 二进制
     
  13. 二进制
     
  14. 二进制
     
  15. 二进制
     
  16. 二进制
     
  17. 二进制
     
  18. 二进制
     
  19. 二进制
     
  20. 二进制
     
  21. 二进制
     
  22. 二进制
     
  23. 二进制
     
  24. 二进制
     
  25. 二进制
     
  26. 二进制
     
  27. 二进制
     
  28. 二进制
     
  29. 二进制
     
  30. 二进制
     
  31. 二进制
     
  32. 二进制
     
  33. 二进制
     
  34. 二进制
     
  35. 二进制
     
  36. +264
    -111
      ui/src/main/res/layout/activity_main.xml
  37. +1
    -0
      ui/src/main/res/raw/progress_loading.json
  38. +1
    -0
      ui/src/main/res/values/colors.xml
  39. +2
    -0
      ui/src/main/res/values/strings.xml

+ 9
- 4
ui/src/main/java/com/getbubblenow/android/activity/BaseActivityBubble.java 查看文件

@@ -8,20 +8,21 @@ import android.view.View;
import android.widget.Toast;

import com.getbubblenow.android.R;
import com.getbubblenow.android.api.ApiConstants;
import com.getbubblenow.android.fragment.ErrorDialogFragment;
import com.getbubblenow.android.fragment.LoadingDialogFragment;
import com.getbubblenow.android.fragment.ErrorDialogFragment;
import com.getbubblenow.android.fragment.LoadingDialogFragment;
import com.getbubblenow.android.repository.DataRepository;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Lifecycle;

public class BaseActivityBubble extends AppCompatActivity {
public class BaseActivityBubble extends AppCompatActivity{

public static final String LOADING_TAG = "loading_tag";
public static final String NO_CONNECTION_TAG = "no_connection_tag";
public static final String ERROR_TAG = "error_tag";
public static final String PROGRESS_TAG = "progress_tag";
public static final String RATE_TAG = "rate tag";
private final long LOADER_DELAY = 1000;

@@ -32,6 +33,11 @@ public class BaseActivityBubble extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DataRepository.getRepositoryInstance() == null) {
DataRepository.buildRepositoryInstance(this, ApiConstants.BOOTSTRAP_URL);
} else {
DataRepository.getRepositoryInstance().buildClientService(ApiConstants.BOOTSTRAP_URL);
}
}


@@ -87,5 +93,4 @@ public class BaseActivityBubble extends AppCompatActivity {
errorDialog.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(errorDialog,ERROR_TAG).commitAllowingStateLoss();
}

}

+ 7
- 83
ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java 查看文件

@@ -1,14 +1,12 @@
package com.getbubblenow.android.activity;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatButton;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import android.content.Intent;
import android.os.Bundle;
import android.security.KeyChain;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -28,7 +26,6 @@ import com.getbubblenow.android.repository.DataRepository;
import com.getbubblenow.android.resource.StatusResource;
import com.getbubblenow.android.viewmodel.LoginViewModel;

import java.util.List;

public class LoginActivity extends BaseActivityBubble {

@@ -37,8 +34,6 @@ public class LoginActivity extends BaseActivityBubble {
private EditText password;
private AppCompatButton sign;

private static final int REQUEST_CODE = 1555;
private static final String CERTIFICATE_NAME = "Bubble Certificate";
private boolean userNameStateFlag = false;
private boolean passwordStateFlag = false;
private static final String USER_NAME_KEY = "userName";
@@ -46,9 +41,6 @@ public class LoginActivity extends BaseActivityBubble {
private static final String NO_INTERNET_CONNECTION = "no internet connection";
private static final String LOGIN_FAILED = "Login Failed";

private String config = "";


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -144,49 +136,22 @@ public class LoginActivity extends BaseActivityBubble {
}

private void signIn() {
if (DataRepository.getRepositoryInstance() == null) {
loginViewModel.buildRepositoryInstance(LoginActivity.this, ApiConstants.BOOTSTRAP_URL);
if (loginViewModel.checkRepositoryInstance()) {
loginViewModel.buildRepositoryInstance(this, ApiConstants.BOOTSTRAP_URL);
} else {
loginViewModel.buildClientService(ApiConstants.BOOTSTRAP_URL);
}
final String usernameInput = userName.getText().toString().trim();
final String passwordInput = password.getText().toString().trim();
showLoadingDialog();
loginViewModel.login(LoginActivity.this,usernameInput,passwordInput).observe(LoginActivity.this, new Observer<StatusResource<byte[]>>() {
loginViewModel.login(this,usernameInput,passwordInput).observe(LoginActivity.this, new Observer<StatusResource<byte[]>>() {
@Override public void onChanged(final StatusResource<byte[]> stringStatusResource) {
switch (stringStatusResource.status) {
case SUCCESS:
byte[] certificateEncode = stringStatusResource.data;
loginViewModel.getConfig(LoginActivity.this).observe(LoginActivity.this, new Observer<StatusResource<String>>() {
@Override public void onChanged(final StatusResource<String> stringStatusResource) {
switch (stringStatusResource.status){
case SUCCESS:
closeLoadingDialog();
config = stringStatusResource.data;
if (loginViewModel.isHaveCertificate(LoginActivity.this)) {
createTunnel();
} else {
final Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, certificateEncode);
intent.putExtra(KeyChain.EXTRA_NAME, CERTIFICATE_NAME);
startActivityForResult(intent, REQUEST_CODE);
}
break;
case LOADING:
break;
case ERROR:
closeLoadingDialog();
if (stringStatusResource.message.equals(NO_INTERNET_CONNECTION)) {
showNetworkNotAvailableMessage();
} else if (stringStatusResource.message.equals(LOGIN_FAILED)) {
Toast.makeText(LoginActivity.this, LOGIN_FAILED, Toast.LENGTH_LONG).show();
} else {
showErrorDialog(stringStatusResource.message);
}
break;
}
}
});
closeLoadingDialog();
final Intent mainActivityIntent = new Intent(LoginActivity.this, MainActivity.class);
mainActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(mainActivityIntent);
break;
case LOADING:
Log.d("TAG", "Loading");
@@ -217,47 +182,6 @@ public class LoginActivity extends BaseActivityBubble {
}
}

@Override protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
createTunnel();
}
else {
Toast.makeText(this, getString(R.string.cerificate_install), Toast.LENGTH_LONG).show();
}
}

private void createTunnel(){
showLoadingDialog();
loginViewModel.createTunnel(this,config).observe(this, new Observer<StatusResource<Object>>() {
@Override public void onChanged(final StatusResource<Object> objectStatusResource) {
closeLoadingDialog();
switch (objectStatusResource.status){
case SUCCESS:
Toast.makeText(LoginActivity.this, getString(R.string.success), Toast.LENGTH_SHORT).show();
Log.d("TAG", "Success");
final Intent mainActivityIntent = new Intent(LoginActivity.this, MainActivity.class);
mainActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(mainActivityIntent);
break;
case LOADING:
break;
case ERROR:
if(objectStatusResource.message.equals(NO_INTERNET_CONNECTION)){
showNetworkNotAvailableMessage();
}
else if(objectStatusResource.message.equals(LOGIN_FAILED)){
Toast.makeText(LoginActivity.this,LOGIN_FAILED,Toast.LENGTH_LONG).show();
}
else {
showErrorDialog(objectStatusResource.message);
}
break;
}
}
});
}

@Override protected void onSaveInstanceState(@NonNull final Bundle outState) {
outState.putBoolean(USER_NAME_KEY,userNameStateFlag);
outState.putBoolean(PASSWORD_KEY,passwordStateFlag);


+ 259
- 41
ui/src/main/java/com/getbubblenow/android/activity/MainActivity.java 查看文件

@@ -1,24 +1,32 @@
package com.getbubblenow.android.activity;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.security.KeyChain;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.airbnb.lottie.LottieAnimationView;
import com.getbubblenow.android.model.Network;
import com.getbubblenow.android.resource.StatusResource;
import com.getbubblenow.android.util.UserStore;
import com.getbubblenow.android.viewmodel.MainViewModel;
import com.getbubblenow.android.R;
import com.getbubblenow.android.viewmodel.MainViewModel;

public class MainActivity extends AppCompatActivity {
public class MainActivity extends BaseActivityBubble {

private MainViewModel mainViewModel;
private TextView bubbleStatus;
@@ -26,8 +34,8 @@ public class MainActivity extends AppCompatActivity {
private Button connectButton;
private ImageView imageMyBubble;
private ImageView mark;
private ImageButton myBubbleButton;
private ImageButton accountButton;
private ImageButton supportButton;
private TextView logout;
private boolean connectionStateFlag;

@@ -39,32 +47,138 @@ public class MainActivity extends AppCompatActivity {
public static final int CONNECTED_TEXT_VIEW_BOTTOM_MARGIN = 100;
private static final String BASE_URL_PREFIX = "https://";
private static final String BASE_URL_SUFFIX_MY_BUBBLE = ":1443/appLogin?session=";
private static final String URI_MY_BUBBLE = "&uri=/";
private static final String BASE_URL_SUFFIX_ACCOUNT = "/appLogin?session=";
private static final String URI_ACCOUNT = "&uri=/me";
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 String config = "";
private byte[] certificateEncode;
private final String SUPPORT_URL = "https://support.getbubblenow.com";

private ConstraintLayout constraint_layout_connected_title;
private ConstraintLayout constraints_layout_connected_bubble;
private ConstraintLayout constraint_layout_disconnected_title;
private ConstraintLayout constraints_layout_disconnected_bubble;
private ImageView union;
private LottieAnimationView network_progress;
private TextView progress_title;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
if (mainViewModel.isUserLoggedIn(this)) {
setContentView(R.layout.activity_main);
mainViewModel.buildRepositoryInstance(this, mainViewModel.getUserURL(this));
initUI();
} else {
showLoadingDialog();
if (mainViewModel.isHaveSageURL(this)) {
if (mainViewModel.isHaveHostName(this)) {
closeLoadingDialog();
setContentView(R.layout.activity_main);
mainViewModel.buildRepositoryInstance(this, mainViewModel.getSageURL(this));
initUI();
logout.setEnabled(true);
connectButton.setEnabled(true);
} else {
setContentView(R.layout.activity_main);
initUI();
if (mainViewModel.checkRepositoryInstance()) {
mainViewModel.buildRepositoryInstance(this, mainViewModel.getSageURL(this));
getCertificateData();
} else {
mainViewModel.buildClientService(mainViewModel.getSageURL(this));
getCertificateData();
}
}
}
else{
closeLoadingDialog();
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
}
}

private void getCertificateData() {

mainViewModel.getCertificateData(this).observe(this, new Observer<StatusResource<byte[]>>() {
@Override public void onChanged(final StatusResource<byte[]> statusResource) {
switch (statusResource.status) {
case SUCCESS:
certificateEncode = statusResource.data;
setConnectedBubbleUI();
mainViewModel.getConfig(MainActivity.this).observe(MainActivity.this, new Observer<StatusResource<String>>() {
@Override public void onChanged(final StatusResource<String> stringStatusResource) {
switch (stringStatusResource.status) {
case SUCCESS:
config = stringStatusResource.data;
mainViewModel.setConfig(config,MainActivity.this);
if (mainViewModel.isHaveCertificate(MainActivity.this)) {
createTunnel();
} else {
final Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, certificateEncode);
intent.putExtra(KeyChain.EXTRA_NAME, CERTIFICATE_NAME);
closeLoadingDialog();
startActivityForResult(intent, REQUEST_CODE);
}
break;
case LOADING:
break;
case ERROR:
closeLoadingDialog();
if (stringStatusResource.message.equals(NO_INTERNET_CONNECTION)) {
showNetworkNotAvailableMessage();
} else if (stringStatusResource.message.equals(LOGIN_FAILED)) {
Toast.makeText(MainActivity.this, LOGIN_FAILED, Toast.LENGTH_LONG).show();
} else {
showErrorDialog(stringStatusResource.message);
}
break;
}
}
});
break;
case LOADING:
break;
case ERROR:
if (statusResource.message.equals(NO_INTERNET_CONNECTION)) {
showNetworkNotAvailableMessage();
} else if (statusResource.message.equals(LOGIN_FAILED)) {
Toast.makeText(MainActivity.this, LOGIN_FAILED, Toast.LENGTH_LONG).show();
} else {
showErrorDialog(statusResource.message);
}
break;
}
}
});

mainViewModel.getNetworkStatusLiveData().observe(this, new Observer<StatusResource<Network>>() {
@Override public void onChanged(final StatusResource<Network> networkStatusResource) {
if (!networkStatusResource.data.isRunning()) {
setProgressBubbleUI();
mainViewModel.getProgressLiveData().observe(MainActivity.this, new Observer<Long>() {
@Override public void onChanged(final Long percent) {
setProgress(percent);
}
});
}
}
});
}

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

@@ -77,11 +191,18 @@ public class MainActivity extends AppCompatActivity {
bubbleStatus = findViewById(R.id.bubbleStatus);
connectButton = findViewById(R.id.connectButton);
imageMyBubble = findViewById(R.id.imageMyBubble);
supportButton = findViewById(R.id.supportButton);
mark = findViewById(R.id.mark);
titleMyBubble = findViewById(R.id.titleMyBubble);
myBubbleButton = findViewById(R.id.myBubbleButton);
accountButton = findViewById(R.id.accountButton);
logout = findViewById(R.id.logout);
constraint_layout_connected_title = findViewById(R.id.constraint_layout_connected_title);
constraints_layout_connected_bubble = findViewById(R.id.constraints_layout_connected_bubble);
constraint_layout_disconnected_title = findViewById(R.id.constraint_layout_disconnected_title);
constraints_layout_disconnected_bubble = findViewById(R.id.constraints_layout_disconnected_bubble);
union = findViewById(R.id.union);
network_progress = findViewById(R.id.network_progress);
progress_title = findViewById(R.id.progress_title);
}

private void initListeners() {
@@ -90,11 +211,6 @@ public class MainActivity extends AppCompatActivity {
connect();
}
});
myBubbleButton.setOnClickListener(new OnClickListener() {
@Override public void onClick(final View v) {
showMyBubble();
}
});
accountButton.setOnClickListener(new OnClickListener() {
@Override public void onClick(final View v) {
showAccount();
@@ -105,32 +221,96 @@ public class MainActivity extends AppCompatActivity {
logout();
}
});
supportButton.setOnClickListener(new OnClickListener() {
@Override public void onClick(final View v) {
showSupport();
}
});
}

private void showAccount() {
String token = mainViewModel.getToken(this);
final String hostname = mainViewModel.getHostname(this);
final String url = BASE_URL_PREFIX + hostname + BASE_URL_SUFFIX_MY_BUBBLE + token + URI_ACCOUNT;
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
final String hostname = mainViewModel.getHostNameForAccount(this);
if(!TextUtils.isEmpty(hostname)) {
String token = mainViewModel.getToken(this);
final String url = BASE_URL_PREFIX + hostname + BASE_URL_SUFFIX_MY_BUBBLE + token + URI_ACCOUNT;
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
}
else {
String sageToken = mainViewModel.getSageToken(this);
final String sageHostname = mainViewModel.getSageHostname(this);
final String url = sageHostname + BASE_URL_SUFFIX_ACCOUNT + sageToken + URI_ACCOUNT;
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
}
}

private void showMyBubble() {
String token = mainViewModel.getToken(this);
final String hostname = mainViewModel.getHostname(this);
final String url = BASE_URL_PREFIX + hostname + BASE_URL_SUFFIX_MY_BUBBLE + token + URI_MY_BUBBLE;
private void showSupport(){
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
intent.setData(Uri.parse(SUPPORT_URL));
startActivity(intent);
}

private void connect() {
final boolean state = mainViewModel.isVPNConnected(this, connectionStateFlag);
connectionStateFlag = state;
mainViewModel.connect(state, MainActivity.this).observe(MainActivity.this, new Observer<Boolean>() {
@Override public void onChanged(final Boolean state) {
setConnectionStateUI(state);
if(mainViewModel.isHaveCertificate(this)) {
mainViewModel.isHaveTunnel(this).observe(this, new Observer<StatusResource<Boolean>>() {
@Override public void onChanged(final StatusResource<Boolean> booleanStatusResource) {
if(booleanStatusResource.data!=null){
if (booleanStatusResource.data) {
final boolean state = mainViewModel.isVPNConnected(MainActivity.this, connectionStateFlag);
connectionStateFlag = state;
mainViewModel.connect(state, MainActivity.this).observe(MainActivity.this, new Observer<Boolean>() {
@Override public void onChanged(final Boolean state) {
setConnectionStateUI(state);
}
});
} else {
createTunnel();
connect();
}
}
}
});

}
else {
final Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, certificateEncode);
intent.putExtra(KeyChain.EXTRA_NAME, CERTIFICATE_NAME);
startActivityForResult(intent, REQUEST_CODE);
}
}

private void createTunnel(){
showLoadingDialog();
mainViewModel.createTunnel(this,UserStore.getInstance(this).getConfig()).observe(this, new Observer<StatusResource<Object>>() {
@Override public void onChanged(final StatusResource<Object> objectStatusResource) {
closeLoadingDialog();
switch (objectStatusResource.status){
case SUCCESS:
closeLoadingDialog();
logout.setEnabled(true);
connectButton.setEnabled(true);
Toast.makeText(MainActivity.this, getString(R.string.success), Toast.LENGTH_SHORT).show();
Log.d("TAG", "Success");
break;
case LOADING:
break;
case ERROR:
closeLoadingDialog();
if(objectStatusResource.message.equals(NO_INTERNET_CONNECTION)){
showNetworkNotAvailableMessage();
}
else if(objectStatusResource.message.equals(LOGIN_FAILED)){
Toast.makeText(MainActivity.this,LOGIN_FAILED,Toast.LENGTH_LONG).show();
}
else {
showErrorDialog(objectStatusResource.message);
}
break;
}
}
});
}
@@ -150,6 +330,16 @@ public class MainActivity extends AppCompatActivity {
setConnectionStateUI(false);
}
}
else if (requestCode == REQUEST_CODE) {
if(resultCode == RESULT_OK) {
createTunnel();
}
else {
logout.setEnabled(true);
connectButton.setEnabled(true);
Toast.makeText(this, getString(R.string.cerificate_install), Toast.LENGTH_LONG).show();
}
}
}

private void setConnectionStateUI(boolean state) {
@@ -172,11 +362,39 @@ public class MainActivity extends AppCompatActivity {
}
}

private void logout(){
final Intent intent = new Intent(MainActivity.this, LoginActivity.class);
private void logout() {
final Intent intent = new Intent(this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mainViewModel.deleteTunnel(this);
mainViewModel.removeSharedPreferences(MainActivity.this);
mainViewModel.removeSharedPreferences(this);
startActivity(intent);
}

private void setConnectedBubbleUI() {
union.setImageResource(R.drawable.union);
constraint_layout_connected_title.setVisibility(View.VISIBLE);
constraints_layout_connected_bubble.setVisibility(View.VISIBLE);
constraint_layout_disconnected_title.setVisibility(View.GONE);
constraints_layout_disconnected_bubble.setVisibility(View.GONE);
}

private void setProgressBubbleUI() {
closeLoadingDialog();
union.setImageResource(R.drawable.progress_background);
constraint_layout_connected_title.setVisibility(View.GONE);
constraints_layout_connected_bubble.setVisibility(View.GONE);
constraint_layout_disconnected_title.setVisibility(View.VISIBLE);
constraints_layout_disconnected_bubble.setVisibility(View.VISIBLE);
}

private void setProgress(long percent) {
final long textProgress = percent;
if (percent % 10 >= 5) {
percent = percent + 10 - (percent % 10);
} else {
percent -= (percent % 10);
}
network_progress.setProgress(percent / 100f);
progress_title.setText(textProgress + "%");
}
}

+ 1
- 0
ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java 查看文件

@@ -16,4 +16,5 @@ public class ApiConstants {
public static final String DEVICE_NAME = "name";
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";
}

+ 4
- 0
ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java 查看文件

@@ -2,6 +2,7 @@ package com.getbubblenow.android.api.network;

import com.getbubblenow.android.model.Device;
import com.getbubblenow.android.model.Network;
import com.getbubblenow.android.model.NetworkStatus;
import com.getbubblenow.android.model.Sages;
import com.getbubblenow.android.model.User;
import com.getbubblenow.android.api.ApiConstants;
@@ -47,4 +48,7 @@ public interface ClientApi {

@GET(ApiConstants.NODE_BASE_URI)
Single<List<Network>> getNodeBaseURI(@HeaderMap HashMap<String,String> header);

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

+ 4
- 0
ui/src/main/java/com/getbubblenow/android/model/Network.java 查看文件

@@ -225,4 +225,8 @@ public class Network {
public void setShortId(final String shortId) {
this.shortId = shortId;
}

public boolean isRunning(){
return state.equals("running");
}
}

+ 78
- 0
ui/src/main/java/com/getbubblenow/android/model/NetworkStatus.java 查看文件

@@ -0,0 +1,78 @@
package com.getbubblenow.android.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class NetworkStatus {

@SerializedName("account")
@Expose
private String account;
@SerializedName("network")
@Expose
private String network;
@SerializedName("pattern")
@Expose
private String pattern;
@SerializedName("match")
@Expose
private String match;
@SerializedName("percent")
@Expose
private long percent;
@SerializedName("messageKey")
@Expose
private String messageKey;
@SerializedName("details")
@Expose
private String details;

public String getPattern() {
return pattern;
}

public void setPattern(final String pattern) {
this.pattern = pattern;
}

public String getMatch() {
return match;
}

public void setMatch(final String match) {
this.match = match;
}

public String getDetails() {
return details;
}

public void setDetails(final String details) {
this.details = details;
}

public String getAccount() {
return account;
}
public void setAccount(final String account) {
this.account = account;
}
public String getNetwork() {
return network;
}
public void setNetwork(final String network) {
this.network = network;
}
public long getPercent() {
return percent;
}
public void setPercent(final long percent) {
this.percent = percent;
}
public String getMessageKey() {
return messageKey;
}
public void setMessageKey(final String messageKey) {
this.messageKey = messageKey;
}
}

+ 289
- 166
ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java 查看文件

@@ -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;
@@ -77,12 +78,16 @@ public class DataRepository {
private static final String RUNNING = "running";
private static final String BASE_URL_PREFIX = "https://";
private static final String BASE_URL_SUFFIX = ":1443/api/";
private static String token = "";
private static String deviceName;
private static String deviceID;
private List<String> nodes;
private int nodeIndex = 0;
private String hostname = "";
private MutableLiveData<StatusResource<Network>> networkStatusLiveData = new MutableLiveData<>();
private MutableLiveData<Long> progressLiveData = new MutableLiveData<>();
private Network _network;
private final long PROGRESS_DELAY = 5000;
private final int FULL_PROGRESS = 100;

private DataRepository(Context context, String url) {
BASE_URL = url;
@@ -110,108 +115,185 @@ public class DataRepository {
return instance;
}

private void getNodeIndex(int index, String username , String password, Context context,NetworkBoundStatusResource<byte[]> liveData){
if(index<nodes.size())
{
getNetwork(nodes.get(index),username,password,context,liveData);
}
else {
private void getNodeIndex(int index, String username, String password, Context context, NetworkBoundStatusResource<byte[]> liveData) {
if (index < nodes.size()) {
getNetwork(nodes.get(index), username, password, context, liveData);
} else {
liveData.postMutableLiveData(StatusResource.error(LOGIN_FAILED));
}
}

private void getNetwork(String node, String username , String password, Context context,NetworkBoundStatusResource<byte[]> liveData){
public MutableLiveData<StatusResource<Network>> getNetworkStatusLiveData() {
return networkStatusLiveData;
}

public MutableLiveData<Long> getProgressLiveData() {
return progressLiveData;
}

private void getNetwork(String node, String username, String password, Context context, NetworkBoundStatusResource<byte[]> liveData) {

HashMap<String, String> data = new HashMap<>();
data.put(ApiConstants.USERNAME, username);
data.put(ApiConstants.PASSWORD, password);
buildClientService(node + URL_SUFFIX);
Disposable disposableLogin = clientApi.login(data)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> {
UserStore.getInstance(context).setSageToken(user.getToken());
// UserStore.getInstance(context).setToken(user.getToken());
UserStore.getInstance(context).setUserData(username, password);
UserStore.getInstance(context).setSageURL(node + URL_SUFFIX);
UserStore.getInstance(context).setSageHostname(node);
BASE_URL = node + URL_SUFFIX;
ApiConstants.BASE_URL = node + URL_SUFFIX;
buildClientService(ApiConstants.BASE_URL);
liveData.postMutableLiveData(StatusResource.success(null));
}, throwable -> {
nodeIndex++;
getNodeIndex(nodeIndex, username, password, context, liveData);
Log.d("ERR", "getSages-login");
});
compositeDisposable.add(disposableLogin);
}

HashMap<String, String> data = new HashMap<>();
data.put(ApiConstants.USERNAME, username);
data.put(ApiConstants.PASSWORD, password);
buildClientService(node + URL_SUFFIX);
Disposable disposableLogin = clientApi.login(data)
.subscribeOn(Schedulers.newThread())

public MutableLiveData<StatusResource<byte[]>> getCertificateData(Context context) {
return new NetworkBoundStatusResource<byte[]>() {

@Override protected void createCall() {
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken());
NetworkBoundStatusResource<byte[]> liveData = this;
buildClientService(UserStore.getInstance(context).getSageURL());

Disposable getNodeBaseURIDisposable = clientApi.getNodeBaseURI(header)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> {
token = user.getToken();
ApiConstants.BASE_URL = node + URL_SUFFIX;
buildClientService(ApiConstants.BASE_URL);
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, token);
Disposable getNodeBaseURIDisposable = clientApi.getNodeBaseURI(header)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(networks -> {
for (Network network : networks) {
if (network.getState().equals(RUNNING)) {
ApiConstants.BASE_URL = network.getName() + "." + network.getDomainName();
BASE_URL = ApiConstants.BASE_URL;
hostname = BASE_URL;
setHostName(context,BASE_URL);
buildClientService(BASE_URL_PREFIX + BASE_URL + BASE_URL_SUFFIX);
setUserURL(context, BASE_URL_PREFIX + BASE_URL + BASE_URL_SUFFIX);
login(username,password,context,liveData);
break;
}
.subscribe(networks -> {
for (Network network : networks) {
if (network.getState().equals(RUNNING)) {
ApiConstants.BASE_URL = network.getName() + "." + network.getDomainName();
UserStore.getInstance(context).setHostNameForAccount(network.getName() + "." + network.getDomainName());
hostname = network.getName() + "." + network.getDomainName();
BASE_URL = BASE_URL_PREFIX + ApiConstants.BASE_URL + BASE_URL_SUFFIX;
buildClientService(BASE_URL);
_network = network;
login(UserStore.getInstance(context).getUsername(), UserStore.getInstance(context).getPassword(), context, this);
} else {
networkStatusLiveData.postValue(StatusResource.success(network));
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override public void run() {
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken());
Disposable getNetworkStateDisposable = clientApi.getNetworkState(UserStore.getInstance(context).getUsername(), network.getUuid(), header)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(networkStatus -> {
int indexNetworkState = 0;
for (int i = 0; i < networkStatus.size(); i++) {
if (networkStatus.get(i).getNetwork().equals(network.getUuid())) {
indexNetworkState = i;
break;
} else {
//TODO Handle when don't have equals network
}
}
if(networkStatus.get(indexNetworkState).getPercent() != FULL_PROGRESS){
progressLiveData.postValue(networkStatus.get(indexNetworkState).getPercent());
handler.postDelayed(this, PROGRESS_DELAY);
}
else if(networkStatus.get(indexNetworkState).getPercent() == FULL_PROGRESS) {
progressLiveData.postValue(networkStatus.get(indexNetworkState).getPercent());
Disposable getNodeBaseURIDisposable = clientApi.getNodeBaseURI(header)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(networks -> {
for (Network network : networks) {
if (network.getState().equals(RUNNING)) {
handler.removeCallbacks(this);
_network = network;
ApiConstants.BASE_URL = network.getName() + "." + network.getDomainName();
UserStore.getInstance(context).setHostNameForAccount(network.getName() + "." + network.getDomainName());
hostname = network.getName() + "." + network.getDomainName();
BASE_URL = ApiConstants.BASE_URL;
buildClientService(BASE_URL_PREFIX + BASE_URL + BASE_URL_SUFFIX);
login(UserStore.getInstance(context).getUsername(), UserStore.getInstance(context).getPassword(), context, liveData);
}
else {
handler.postDelayed(this, PROGRESS_DELAY);
}
}
}, throwable -> {
Log.d("ERR", "getNodeBaseURI");
setErrorMessage(throwable, liveData);
});
}
}, throwable -> {
Log.d("ERR", "getNetworkState");
setErrorMessage(throwable, liveData);
});
}
}, throwable -> {
Log.d("ERR", "getNodeBaseURI");
nodeIndex++;
getNodeIndex(nodeIndex,username,password,context,liveData);
});
compositeDisposable.add(getNodeBaseURIDisposable);
}, PROGRESS_DELAY);
}
}
}, throwable -> {
nodeIndex++;
getNodeIndex(nodeIndex,username,password,context,liveData);
Log.d("ERR", "getSages-login");
Log.d("ERR", "getNodeBaseURI2");
setErrorMessage(throwable, liveData);
});
compositeDisposable.add(disposableLogin);

}
}.getMutableLiveData();
}


public MutableLiveData<StatusResource<byte[]>> login(Context context, String username, String password){
return new NetworkBoundStatusResource<byte[]>(){
public MutableLiveData<StatusResource<byte[]>> login(Context context, String username, String password) {
return new NetworkBoundStatusResource<byte[]>() {

@Override protected void createCall() {
Disposable sagesDisposable = clientApi.getSages()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(sages -> {
nodeIndex = 0;
nodes = sages.getSages();
getNodeIndex(nodeIndex,username,password,context,this);
},throwable -> {
Log.d("ERR","getSages");
setErrorMessage(throwable,this);
});
compositeDisposable.add(sagesDisposable);
}
}.getMutableLiveData();
@Override protected void createCall() {
Disposable sagesDisposable = clientApi.getSages()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(sages -> {
nodeIndex = 0;
nodes = sages.getSages();
getNodeIndex(nodeIndex, username, password, context, this);
}, throwable -> {
Log.d("ERR", "getSages");
setErrorMessage(throwable, this);
});
compositeDisposable.add(sagesDisposable);
}
}.getMutableLiveData();
}

private MutableLiveData<StatusResource<byte[]>> login(String username, String password, Context context, NetworkBoundStatusResource<byte[]> liveData) {
return new NetworkBoundStatusResource<byte[]>() {
@Override protected void createCall() {
HashMap<String, String> data = new HashMap<>();
buildClientService(BASE_URL);
data.put(ApiConstants.USERNAME, username);
data.put(ApiConstants.PASSWORD, password);
Disposable disposableLogin = clientApi.login(data)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> {
token = user.getToken();
UserStore.getInstance(context).setToken(user.getToken());
if (!isDeviceLoggedIn(context)) {
addDevice(context);
} else {
getAllDevices(context);
}
}, throwable -> {
setErrorMessage(throwable,liveData);
Log.d("ERR", "login");
setErrorMessage(throwable, liveData);
});
compositeDisposable.add(disposableLogin);
}

private void getAllDevices(final Context context) {
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, token);
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getToken());
Disposable disposableAllDevices = clientApi.getAllDevices(header)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
@@ -221,6 +303,7 @@ public class DataRepository {
//TODO Add errbit for this case
if (UserStore.getInstance(context).getDeviceID().equals(item.getUuid())) {
// UserStore.getInstance(context).setToken(token);
Log.i("INF", "SUCCESS NULL");
setMutableLiveData(StatusResource.success(null));
hasDevice = true;
break;
@@ -230,7 +313,8 @@ public class DataRepository {
addDevice(context);
}
}, throwable -> {
setErrorMessage(throwable,liveData);
Log.d("ERR", "getAllDevices");
setErrorMessage(throwable, liveData);
});
compositeDisposable.add(disposableAllDevices);
}
@@ -241,7 +325,7 @@ public class DataRepository {
final String imei = getDeviceID(context);
final String deviceName = brand + SPACE + model + SPACE + SEPARATOR + SPACE + imei;
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, token);
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getToken());
final Disposable disposableAllDevices = clientApi.getAllDevices(header)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
@@ -260,9 +344,10 @@ public class DataRepository {
hasDevice = true;
getCertificate(context).observe((LifecycleOwner) context, new Observer<StatusResource<byte[]>>() {
@Override public void onChanged(final StatusResource<byte[]> statusResource) {
switch (statusResource.status){
switch (statusResource.status) {
case SUCCESS:
liveData.postMutableLiveData(StatusResource.success(statusResource.data));
networkStatusLiveData.postValue(StatusResource.success(_network));
break;
case ERROR:
liveData.postMutableLiveData(StatusResource.error(statusResource.message));
@@ -293,12 +378,13 @@ public class DataRepository {
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(device -> {
UserStore.getInstance(context).setDevice(device.getName(), device.getUuid());
deviceID = device.getUuid();
getCertificate(context).observe((LifecycleOwner) context, new Observer<StatusResource<byte[]>>() {
@Override public void onChanged(final StatusResource<byte[]> statusResource) {
switch (statusResource.status){
switch (statusResource.status) {
case SUCCESS:
liveData.postMutableLiveData(StatusResource.success(statusResource.data));
networkStatusLiveData.postValue(StatusResource.success(_network));
break;
case ERROR:
liveData.postMutableLiveData(StatusResource.error(statusResource.message));
@@ -308,8 +394,9 @@ public class DataRepository {
});
// getConfig(context);
}, throwable -> {
setErrorMessage(throwable,liveData);
// setMutableLiveData(StatusResource.error(throwable.getMessage()));
setErrorMessage(throwable, liveData);
Log.d("ERR", "addDevice5");
// setMutableLiveData(StatusResource.error(throwable.getMessage()));
});
compositeDisposable.add(disposableAddDevice);
} else {
@@ -346,12 +433,13 @@ public class DataRepository {
.subscribe(device -> {
DataRepository.deviceName = device.getName();
DataRepository.deviceID = device.getUuid();
// UserStore.getInstance(context).setDevice(device.getName(), device.getUuid());
// UserStore.getInstance(context).setDevice(device.getName(), device.getUuid());
getCertificate(context).observe((LifecycleOwner) context, new Observer<StatusResource<byte[]>>() {
@Override public void onChanged(final StatusResource<byte[]> statusResource) {
switch (statusResource.status){
switch (statusResource.status) {
case SUCCESS:
liveData.postMutableLiveData(StatusResource.success(statusResource.data));
networkStatusLiveData.postValue(StatusResource.success(_network));
break;
case ERROR:
liveData.postMutableLiveData(StatusResource.error(statusResource.message));
@@ -361,16 +449,18 @@ public class DataRepository {
});
// getConfig(context);
}, throwable -> {
setErrorMessage(throwable,liveData);
// setMutableLiveData(StatusResource.error(throwable.getMessage()));
Log.d("ERR", "addDevice10");
setErrorMessage(throwable, liveData);
// setMutableLiveData(StatusResource.error(throwable.getMessage()));
});
compositeDisposable.add(disposableAddDevice);
}
}
}
}, throwable -> {
setErrorMessage(throwable,liveData);
// setMutableLiveData(StatusResource.error(NO_INTERNET_CONNECTION));
setErrorMessage(throwable, liveData);
Log.d("ERR", "getAllDevices2");
// setMutableLiveData(StatusResource.error(NO_INTERNET_CONNECTION));
});
compositeDisposable.add(disposableAllDevices);
}
@@ -378,31 +468,34 @@ public class DataRepository {
}


public MutableLiveData<StatusResource<String>> getConfig(Context context){
return new NetworkBoundStatusResource<String>(){
public MutableLiveData<StatusResource<String>> getConfig(Context context) {
return new NetworkBoundStatusResource<String>() {

@Override protected void createCall() {
NetworkBoundStatusResource<String> liveData = this;

final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, token);
final Disposable configDisposable = clientApi.getConfig(deviceID,header)
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getToken());
buildClientService(BASE_URL);
final Disposable configDisposable = clientApi.getConfig(deviceID, header)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dataConfig->{
.subscribe(dataConfig -> {
final InputStream inputStream = dataConfig.byteStream();
final Scanner scanner = new Scanner(inputStream).useDelimiter(DELIMITER);
final String data = scanner.hasNext() ? scanner.next() : "";
postMutableLiveData(StatusResource.success(data));
},throwable -> {
setErrorMessage(throwable,this);
}, throwable -> {
Log.d("ERR", "getConfig");
setErrorMessage(throwable, this);
});
compositeDisposable.add(configDisposable);
}
}.getMutableLiveData();
}

public MutableLiveData<StatusResource<Object>> createTunnel(Context context, String dataConfig){
return new NetworkBoundStatusResource<Object>(){
public MutableLiveData<StatusResource<Object>> createTunnel(Context context, String dataConfig) {
return new NetworkBoundStatusResource<Object>() {

@Override protected void createCall() {
try {
@@ -412,14 +505,17 @@ public class DataRepository {
if (observableTunnel != null) {
TunnelStore.getInstance(context).setTunnel(TUNNEL_NAME, dataConfig);
Application.getTunnelManager().setTunnelState(observableTunnel, State.DOWN);
UserStore.getInstance(context).setToken(token);
UserStore.getInstance(context).setToken(UserStore.getInstance(context).getToken());
UserStore.getInstance(context).setDevice(deviceName, deviceID);
setHostName(context, ApiConstants.BASE_URL);
postMutableLiveData(StatusResource.success(null));
} else {
Log.d("ERR", "createTunnel11");
setErrorMessage(throwable, this);
}
});
} catch (Exception e) {
Log.d("ERR", "createTunnel12");
setErrorMessage(e, this);
}
}
@@ -463,7 +559,7 @@ public class DataRepository {
}

public void clearDisposable() {
if(compositeDisposable!=null) {
if (compositeDisposable != null) {
compositeDisposable.clear();
}
}
@@ -496,10 +592,6 @@ public class DataRepository {
return tunnel;
}

public void setUserURL(Context context, String url) {
UserStore.getInstance(context).setUserURL(url);
}

public ObservableTunnel getTunnel(Context context, boolean connectionStateFlag) {
ObservableTunnel tunnel = Application.getTunnelManager().getLastUsedTunnel();
if (tunnel == null) {
@@ -509,18 +601,18 @@ public class DataRepository {
return tunnel;
}

public List<String> getAllCertificates(final Context context){
public List<String> getAllCertificates(final Context context) {
final List<String> certificates = new ArrayList<>();
try {
final KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
if(keyStore!=null){
keyStore.load(null,null);
if (keyStore != null) {
keyStore.load(null, null);
final Enumeration<String> aliases = keyStore.aliases();

while (aliases.hasMoreElements()){
while (aliases.hasMoreElements()) {
final String alias = aliases.nextElement();
final java.security.cert.X509Certificate certificate = (java.security.cert.X509Certificate)keyStore.getCertificate(alias);
certificates.add(certificate.getIssuerDN().getName());
final java.security.cert.X509Certificate certificate = (java.security.cert.X509Certificate) keyStore.getCertificate(alias);
certificates.add(certificate.getIssuerDN().getName());
}
}
} catch (final KeyStoreException | NoSuchAlgorithmException | java.security.cert.CertificateException | IOException e) {
@@ -532,40 +624,43 @@ public class DataRepository {
private MutableLiveData<StatusResource<byte[]>> getCertificate(Context context) {
return new NetworkBoundStatusResource<byte[]>() {
@Override protected void createCall() {
final Disposable certificateDisposable = clientApi.getCertificate()
final Disposable certificateDisposable = clientApi.getCertificate()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(certificate->{
.subscribe(certificate -> {
final InputStream inputStream = certificate.byteStream();
final Scanner scanner = new Scanner(inputStream).useDelimiter(DELIMITER);
final String data = scanner.hasNext() ? scanner.next() : "";
final byte[] cert = data.getBytes();
X509Certificate x509Certificate = null;
try {
x509Certificate = X509Certificate.getInstance(cert);
} catch (final CertificateException e) {
setErrorMessage(e, this);
}
try {
if (x509Certificate != null) {
String name = x509Certificate.getSubjectDN().getName();
postMutableLiveData(StatusResource.success(x509Certificate.getEncoded()));
}
} catch (final CertificateEncodingException e) {
setErrorMessage(e, this);
}
},throwable -> {
setErrorMessage(throwable,this);
final Scanner scanner = new Scanner(inputStream).useDelimiter(DELIMITER);
final String data = scanner.hasNext() ? scanner.next() : "";
final byte[] cert = data.getBytes();
X509Certificate x509Certificate = null;
try {
x509Certificate = X509Certificate.getInstance(cert);
} catch (final CertificateException e) {
Log.d("ERR", "getCertificate12");
setErrorMessage(e, this);
}
try {
if (x509Certificate != null) {
String name = x509Certificate.getSubjectDN().getName();
postMutableLiveData(StatusResource.success(x509Certificate.getEncoded()));
}
} catch (final CertificateEncodingException e) {
Log.d("ERR", "getCertificate13");
setErrorMessage(e, this);
}
}, throwable -> {
Log.d("ERR", "getCertificate14");
setErrorMessage(throwable, this);
});
compositeDisposable.add(certificateDisposable);
compositeDisposable.add(certificateDisposable);
}
}.getMutableLiveData();
}

public boolean isHaveCertificate(Context context){
public boolean isHaveCertificate(Context context) {
final List<String> certificates = getAllCertificates(context);
for (final String certificate:certificates) {
if(certificate.contains("."+hostname)) {
for (final String certificate : certificates) {
if (certificate.contains("." + hostname)) {
return true;
}
}
@@ -577,20 +672,20 @@ public class DataRepository {
return pendingTunnel.getState() == State.DOWN;
}

public MutableLiveData<Boolean> connect(final Boolean checked , Context context) {
public MutableLiveData<Boolean> connect(final Boolean checked, Context context) {
MutableLiveData<Boolean> liveData = new MutableLiveData<>();
if (pendingTunnel != null) {
Application.getBackendAsync().thenAccept(backend -> {
if (backend instanceof GoBackend) {
final Intent intent = GoBackend.VpnService.prepare(context);
if (intent != null) {
if(context instanceof MainActivity) {
((MainActivity)context).startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION);
if (context instanceof MainActivity) {
((MainActivity) context).startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION);
return;
}
}
}
connectWithPermission(checked,context).observe((LifecycleOwner) context, new Observer<Boolean>() {
connectWithPermission(checked, context).observe((LifecycleOwner) context, new Observer<Boolean>() {
@Override public void onChanged(final Boolean aBoolean) {
liveData.postValue(aBoolean);
}
@@ -600,55 +695,53 @@ public class DataRepository {
return liveData;
}

public MutableLiveData<Boolean> connectWithPermission(final boolean checked , Context context) {
public MutableLiveData<Boolean> connectWithPermission(final boolean checked, Context context) {
MutableLiveData<Boolean> liveData = new MutableLiveData<>();
pendingTunnel.setStateAsync(Tunnel.State.of(checked)).whenComplete((observableTunnel, throwable) ->{
if(throwable==null){
if(observableTunnel == State.DOWN) {
pendingTunnel.setStateAsync(Tunnel.State.of(checked)).whenComplete((observableTunnel, throwable) -> {
if (throwable == null) {
if (observableTunnel == State.DOWN) {
liveData.postValue(false);
}
else {
} else {
liveData.postValue(true);
}
}
else {
Toast.makeText(context,context.getString(R.string.failed_bubble),Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, context.getString(R.string.failed_bubble), Toast.LENGTH_SHORT).show();
}
});
return liveData;
}


public void setHostName(Context context, String hostname){
public void setHostName(Context context, String hostname) {
UserStore.getInstance(context).setHostname(hostname);
}

public String getHostname(Context context){
public String getHostname(Context context) {
return UserStore.getInstance(context).getHostname();
}

private <T> void setErrorMessage(Throwable throwable , NetworkBoundStatusResource<T> liveData){
if (throwable instanceof IOException) {
liveData.postMutableLiveData(StatusResource.error(NO_INTERNET_CONNECTION));
}
if (throwable instanceof HttpException) {
if (((HttpException) throwable).code() == 500) {
final String requestURL = ((HttpException) throwable).response().raw().request().url().toString();
final String requestMethod = ((HttpException) throwable).response().raw().request().method();
final String requestBody = bodyToString(((HttpException) throwable).response().raw().request());
final String stackTrace = Arrays.toString(throwable.getStackTrace());
final String message = "URL:" + requestURL + '\n' +
"BODY:" + requestBody + '\n' +
"METHOD:" + requestMethod + '\n' +
"STACK_TRACE:" + stackTrace;
liveData.postMutableLiveData(StatusResource.error(message));
} else {
liveData.postMutableLiveData(StatusResource.error(LOGIN_FAILED));
}
private <T> void setErrorMessage(Throwable throwable, NetworkBoundStatusResource<T> liveData) {
if (throwable instanceof IOException) {
liveData.postMutableLiveData(StatusResource.error(NO_INTERNET_CONNECTION));
}
if (throwable instanceof HttpException) {
if (((HttpException) throwable).code() == 500) {
final String requestURL = ((HttpException) throwable).response().raw().request().url().toString();
final String requestMethod = ((HttpException) throwable).response().raw().request().method();
final String requestBody = bodyToString(((HttpException) throwable).response().raw().request());
final String stackTrace = Arrays.toString(throwable.getStackTrace());
final String message = "URL:" + requestURL + '\n' +
"BODY:" + requestBody + '\n' +
"METHOD:" + requestMethod + '\n' +
"STACK_TRACE:" + stackTrace;
liveData.postMutableLiveData(StatusResource.error(message));
} else {
liveData.postMutableLiveData(StatusResource.error(LOGIN_FAILED));
}
}
}

private String bodyToString(final Request request){
private String bodyToString(final Request request) {
try {
final Request copy = request.newBuilder().build();
final Buffer buffer = new Buffer();
@@ -659,18 +752,48 @@ public class DataRepository {
}
}

public void removeSharedPreferences(Context context){
context.getSharedPreferences(UserStore.USER_SHARED_PREF,0).edit().clear().apply();
context.getSharedPreferences(TunnelStore.TUNNEL_SHARED_PREF,0).edit().clear().apply();
public void removeSharedPreferences(Context context) {
context.getSharedPreferences(UserStore.USER_SHARED_PREF, 0).edit().clear().apply();
context.getSharedPreferences(TunnelStore.TUNNEL_SHARED_PREF, 0).edit().clear().apply();
}

public void deleteTunnel(Context context){
public void deleteTunnel(Context context) {
ArrayList<ObservableTunnel> tunnels = new ArrayList<>();
tunnels.add(pendingTunnel);
if(pendingTunnel!=null) {
tunnels.add(pendingTunnel);
// UtilKt.deleteTunnel(tunnels);
Application.getTunnelManager().delete(pendingTunnel);
Application.getTunnelManager().delete(pendingTunnel);
}
// ArrayList<ObservableTunnel> tunnels = new ArrayList<>();
// tunnels.add(tunnelManager.getLastUsedTunnel());
// UtilKt.deleteTunnel(tunnels);
}

public MutableLiveData<StatusResource<Boolean>> isHaveTunnel(Context context) {
return new NetworkBoundStatusResource<Boolean>(){
@Override protected void createCall() {
final byte[] configBytes = UserStore.getInstance(context).getConfig().getBytes();
Config config = null;
try {
config = Config.parse(new ByteArrayInputStream(configBytes));
} catch (IOException ex) {
ex.printStackTrace();
} catch (BadConfigException ex) {
ex.printStackTrace();
}
Application.getTunnelManager().create(TUNNEL_NAME, config).whenComplete((observableTunnel, throwable) -> {
if (observableTunnel != null) {
TunnelStore.getInstance(context).setTunnel(TUNNEL_NAME, UserStore.getInstance(context).getConfig());
Application.getTunnelManager().setTunnelState(observableTunnel, State.DOWN);
UserStore.getInstance(context).setToken(UserStore.getInstance(context).getToken());
UserStore.getInstance(context).setDevice(deviceName, deviceID);
setHostName(context, ApiConstants.BASE_URL);
postMutableLiveData(StatusResource.success(false));
} else {
postMutableLiveData(StatusResource.success(true));
}
});
}
}.getMutableLiveData();
}
}

+ 67
- 10
ui/src/main/java/com/getbubblenow/android/util/UserStore.java 查看文件

@@ -11,13 +11,25 @@ public class UserStore {
private static final String USER_DATA_KEY = "com.wireguard.android.util.bubbleUserResponse";
private static final String DEVICE_DATA_KEY = "com.wireguard.android.util.bubbleDeviceResponse";
private static final String DEVICE_ID_KEY = "com.wireguard.android.util.bubbleDeviceIDResponse";
private static final String USER_BASE_URL_KEY = "com.wireguard.android.util.bubbleUserURLResponse";
private static final String HOSTNAME_KEY = "com.wireguard.android.util.bubbleHostnameResponse";
private static final String USERNAME_KEY = "com.wireguard.android.util.bubbleUSERNAMEResponse";
private static final String PASSWORD_KEY = "com.wireguard.android.util.bubblePASSWORDResponse";
private static final String SAGE_TOKEN_KEY = "com.wireguard.android.util.bubbleSageTokenResponse";
private static final String CONFIG_KEY = "com.wireguard.android.util.bubbleConfigResponse";
private static final String SAGE_HOSTNAME_KEY = "com.wireguard.android.util.bubbleSageHostNameResponse";
private static final String SAGE_KEY = "com.wireguard.android.util.bubbleSageResponse";
private static final String HOSTNAME_FOR_ACCOUNT_KEY = "com.wireguard.android.util.bubbleHostNameForAccountResponse";
public static final String USER_TOKEN_DEFAULT_VALUE = "";
public static final String DEVICE_DEFAULT_VALUE = "";
public static final String DEVICE_ID_DEFAULT_VALUE = "";
public static final String USER_BASE_URL_DEFAULT_VALUE = "";
private static final String HOSTNAME_DEFAULT_VALUE = "";
public static final String USERNAME_DEFAULT_VALUE = "";
private static final String PASSWORD_DEFAULT_VALUE = "";
private 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 = "";

public static UserStore getInstance(Context context) {
if (instance == null) {
@@ -56,14 +68,6 @@ public class UserStore {
return sharedPreferences.getString(DEVICE_ID_KEY,DEVICE_ID_DEFAULT_VALUE);
}

public void setUserURL(String url){
sharedPreferences.edit().putString(USER_BASE_URL_KEY,url).apply();
}

public String getUserURL(){
return sharedPreferences.getString(USER_BASE_URL_KEY,USER_BASE_URL_DEFAULT_VALUE);
}

public void setHostname(String hostName){
sharedPreferences.edit().putString(HOSTNAME_KEY,hostName).apply();
}
@@ -71,4 +75,57 @@ public class UserStore {
public String getHostname(){
return sharedPreferences.getString(HOSTNAME_KEY,HOSTNAME_DEFAULT_VALUE);
}

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

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

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

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

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

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

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

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

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

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

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

public void setHostNameForAccount(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);
}
}

+ 4
- 4
ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java 查看文件

@@ -22,10 +22,6 @@ public class LoginViewModel extends ViewModel {
DataRepository.buildRepositoryInstance(context,url);
}

public void setUserURL(Context context, String url){
DataRepository.getRepositoryInstance().setUserURL(context,url);
}

public void buildClientService(String url){
DataRepository.getRepositoryInstance().buildClientService(url);
}
@@ -49,4 +45,8 @@ public class LoginViewModel extends ViewModel {
public boolean isHaveCertificate(Context context){
return DataRepository.getRepositoryInstance().isHaveCertificate(context);
}

public boolean checkRepositoryInstance(){
return DataRepository.getRepositoryInstance() == null;
}
}

+ 66
- 7
ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java 查看文件

@@ -2,11 +2,10 @@ package com.getbubblenow.android.viewmodel;

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.model.TunnelManager;
import com.getbubblenow.android.repository.DataRepository;
import com.getbubblenow.android.util.TunnelStore;
import com.getbubblenow.android.resource.StatusResource;
import com.getbubblenow.android.util.UserStore;

import androidx.lifecycle.MutableLiveData;
@@ -25,10 +24,6 @@ public class MainViewModel extends ViewModel {
DataRepository.buildRepositoryInstance(context,url);
}

public String getUserURL(Context context){
return UserStore.getInstance(context).getUserURL();
}

public MutableLiveData<Boolean> connectWithPermission(final boolean checked , Context context) {
return DataRepository.getRepositoryInstance().connectWithPermission(checked,context);
}
@@ -56,4 +51,68 @@ public class MainViewModel extends ViewModel {
public String getToken(Context context) {
return UserStore.getInstance(context).getToken();
}

public void buildClientService(String url){
DataRepository.getRepositoryInstance().buildClientService(url);
}

public MutableLiveData<StatusResource<String>> getConfig(Context context){
return DataRepository.getRepositoryInstance().getConfig(context);
}

public MutableLiveData<StatusResource<Object>> createTunnel(Context context, String dataConfig){
return DataRepository.getRepositoryInstance().createTunnel(context, dataConfig);
}

public boolean isHaveCertificate(Context context) {
return DataRepository.getRepositoryInstance().isHaveCertificate(context);
}

public boolean isHaveSageURL(Context context){
return !UserStore.getInstance(context).getSageURL().isEmpty();
}

public boolean isHaveHostName(Context context){
return !UserStore.getInstance(context).getHostname().isEmpty();
}

public String getSageURL(Context context){
return UserStore.getInstance(context).getSageURL();
}

public void setConfig(String config, Context context){
UserStore.getInstance(context).setConfig(config);
}

public MutableLiveData<StatusResource<byte[]>> getCertificateData(Context context) {
return DataRepository.getRepositoryInstance().getCertificateData(context);
}

public MutableLiveData<StatusResource<Network>> getNetworkStatusLiveData(){
return DataRepository.getRepositoryInstance().getNetworkStatusLiveData();
}

public MutableLiveData<Long> getProgressLiveData(){
return DataRepository.getRepositoryInstance().getProgressLiveData();
}

public MutableLiveData<StatusResource<Boolean>> isHaveTunnel(Context context) {
return DataRepository.getRepositoryInstance().isHaveTunnel(context);
}

public String getSageHostname(Context context){
return UserStore.getInstance(context).getSageHostname();
}

public boolean checkRepositoryInstance(){
return DataRepository.getRepositoryInstance() == null;
}

public String getSageToken(Context context){
return UserStore.getInstance(context).getSageToken();
}

public String getHostNameForAccount(Context context){
return UserStore.getInstance(context).getHostNameForAccount();
}
}

二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


+ 264
- 111
ui/src/main/res/layout/activity_main.xml 查看文件

@@ -20,158 +20,311 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<TextView
android:id="@+id/bubbleConnectionTitle"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout_connected_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="@dimen/bubble_connection_marginTop"
android:layout_marginTop="@dimen/margin_top"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:fontFamily="@font/josefin_sans"
android:letterSpacing="-0.02"
android:text="@string/my_bubble"
android:textColor="@color/black"
android:textSize="@dimen/bubble_connection_title_text_size"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent">

<TextView
android:id="@+id/bubbleConnectionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="@dimen/bubble_connection_marginTop"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:fontFamily="@font/josefin_sans"
android:letterSpacing="-0.02"
android:text="@string/my_bubble"
android:textColor="@color/black"
android:textSize="@dimen/bubble_connection_title_text_size"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:text="@string/your_private_bubble"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bubbleConnectionTitle" />


<TextView
android:id="@+id/logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:enabled="false"
android:text="@string/log_out"
android:textColor="@color/logout"
android:textSize="@dimen/text_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>

<TextView
android:id="@+id/title"

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraints_layout_connected_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:text="@string/your_private_bubble"
android:textAlignment="center"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toTopOf="@id/constraints_middle"
app:layout_constraintBottom_toTopOf="@+id/constraints_middle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bubbleConnectionTitle" />
app:layout_constraintStart_toStartOf="parent">

<ImageView
android:id="@+id/imageMyBubble"
android:layout_width="285.56dp"
android:layout_height="378.72dp"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:layout_marginBottom="120dp"
android:src="@drawable/bubble_disconnected"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/constraints" />

<TextView
android:id="@+id/logout"
<TextView
android:id="@+id/titleMyBubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="45dp"
android:letterSpacing="-0.05"
android:text="@string/bubble_status"
android:textColor="@android:color/black"
android:textSize="@dimen/myBubble_text_size"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/constraints"
app:layout_constraintEnd_toEndOf="@+id/imageMyBubble"
app:layout_constraintStart_toStartOf="@+id/imageMyBubble" />

<ImageView
android:id="@+id/mark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="80dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:src="@drawable/mark"
android:visibility="gone"

app:layout_constraintEnd_toEndOf="@+id/imageMyBubble"
app:layout_constraintStart_toStartOf="@+id/imageMyBubble"
app:layout_constraintTop_toTopOf="@+id/imageMyBubble" />

<TextView
android:id="@+id/bubbleStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="150dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:layout_marginBottom="120dp"
android:text="@string/not_connected_bubble"
android:textColor="@color/gray"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="@+id/imageMyBubble"
app:layout_constraintStart_toStartOf="@+id/imageMyBubble"
app:layout_constraintTop_toTopOf="@+id/imageMyBubble" />

<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="@dimen/edit_text_width"
android:layout_height="@dimen/edit_text_height"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="100dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:background="@android:color/transparent"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraints">

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/connectButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/sign_in_enable"
android:enabled="false"
android:text="@string/connect"
android:textAlignment="center"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="@dimen/text_title" />
</LinearLayout>

<androidx.constraintlayout.widget.Constraints
android:id="@+id/constraints"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:layout_marginBottom="@dimen/margin_start_and_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout_disconnected_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="8dp"
android:layout_marginTop="@dimen/margin_top"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:text="@string/log_out"
android:textColor="@color/logout"
android:textSize="@dimen/text_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
app:layout_constraintTop_toTopOf="parent"
>

<ImageView
android:id="@+id/imageMyBubble"
android:layout_width="285.56dp"
android:layout_height="378.72dp"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:layout_marginBottom="120dp"
android:src="@drawable/bubble_disconnected"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/constraints" />
<TextView
android:id="@+id/bubble_building_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="@dimen/bubble_connection_marginTop"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:fontFamily="@font/lato"
android:letterSpacing="-0.02"
android:text="@string/building_bubble_title"
android:textAlignment="center"
android:textColor="@color/black"
android:textSize="@dimen/title"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/titleMyBubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="45dp"
android:letterSpacing="-0.05"
android:text="@string/bubble_status"
android:textColor="@android:color/black"
android:textSize="@dimen/myBubble_text_size"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/constraints"
app:layout_constraintEnd_toEndOf="@+id/imageMyBubble"
app:layout_constraintStart_toStartOf="@+id/imageMyBubble" />
<TextView
android:id="@+id/private_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="40dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:fontFamily="@font/lato"
android:letterSpacing="-0.02"
android:text="@string/private_bubble"
android:textAlignment="center"
android:textColor="@color/black"
android:textSize="@dimen/title"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/bubble_building_title" />

<ImageView
android:id="@+id/mark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="80dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:src="@drawable/mark"
android:visibility="gone"
</androidx.constraintlayout.widget.ConstraintLayout>

app:layout_constraintEnd_toEndOf="@+id/imageMyBubble"
app:layout_constraintStart_toStartOf="@+id/imageMyBubble"
app:layout_constraintTop_toTopOf="@+id/imageMyBubble" />

<TextView
android:id="@+id/bubbleStatus"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraints_layout_disconnected_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="150dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:layout_marginBottom="120dp"
android:text="@string/not_connected_bubble"
android:textColor="@color/gray"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="@+id/imageMyBubble"
app:layout_constraintStart_toStartOf="@+id/imageMyBubble"
app:layout_constraintTop_toTopOf="@+id/imageMyBubble" />

<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="@dimen/edit_text_width"
android:layout_height="@dimen/edit_text_height"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="100dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:background="@android:color/transparent"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraints">

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/connectButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/sign_in_enable"
android:enabled="true"
android:text="@string/connect"
android:textAlignment="center"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="@dimen/text_title" />
</LinearLayout>
app:layout_constraintTop_toTopOf="@id/constraints_middle"
app:layout_constraintBottom_toTopOf="@+id/constraints_middle"
>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/network_progress"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:background="@android:color/transparent"
android:backgroundTint="@android:color/transparent"
android:foregroundGravity="center_horizontal"
app:layout_constraintBottom_toBottomOf="@+id/progress_constraints"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/progress_constraints"
app:lottie_rawRes="@raw/progress_loading" />

<TextView
android:id="@+id/progress_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="250dp"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:fontFamily="@font/lato"
android:paddingTop="10dp"
android:textColor="@android:color/black"
android:textStyle="bold"
android:textSize="@dimen/myBubble_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/network_progress" />

<androidx.constraintlayout.widget.Constraints
android:id="@+id/progress_constraints"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginTop="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:layout_marginBottom="@dimen/margin_start_and_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

<ImageButton
android:id="@+id/myBubbleButton"
android:id="@+id/accountButton"
android:layout_width="@dimen/icons_size"
android:layout_height="@dimen/icons_size"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginBottom="44dp"
android:background="@drawable/my_bubble"
android:background="@drawable/account"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/accountButton"
app:layout_constraintEnd_toStartOf="@+id/supportButton"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />

<ImageButton
android:id="@+id/accountButton"
android:id="@+id/supportButton"
android:layout_width="@dimen/icons_size"
android:layout_height="@dimen/icons_size"
android:layout_marginStart="@dimen/margin_start_and_end"
android:layout_marginEnd="@dimen/margin_start_and_end"
android:background="@drawable/account"
app:layout_constraintBottom_toBottomOf="@+id/myBubbleButton"
android:background="@drawable/support"
app:layout_constraintBottom_toBottomOf="@+id/accountButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/myBubbleButton"
app:layout_constraintTop_toTopOf="@+id/myBubbleButton" />
app:layout_constraintStart_toEndOf="@+id/accountButton"
app:layout_constraintTop_toTopOf="@+id/accountButton" />

<ImageButton
android:id="@+id/imageButton"
@@ -180,12 +333,12 @@
android:layout_marginTop="32dp"
android:background="@drawable/home"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/accountButton"
app:layout_constraintStart_toEndOf="@+id/myBubbleButton"
app:layout_constraintTop_toTopOf="@+id/myBubbleButton" />
app:layout_constraintEnd_toStartOf="@+id/supportButton"
app:layout_constraintStart_toEndOf="@+id/accountButton"
app:layout_constraintTop_toTopOf="@+id/accountButton" />

<androidx.constraintlayout.widget.Constraints
android:id="@+id/constraints"
android:id="@+id/constraints_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_start_and_end"


+ 1
- 0
ui/src/main/res/raw/progress_loading.json
文件差异内容过多而无法显示
查看文件


+ 1
- 0
ui/src/main/res/values/colors.xml 查看文件

@@ -44,4 +44,5 @@
<color name="sign_in_enable_end_color">#C037C0</color>
<color name="toast_color">#4E4E4E</color>
<color name="logout">#159F97</color>
<color name="background_progress">#F7F8FC</color>
</resources>

+ 2
- 0
ui/src/main/res/values/strings.xml 查看文件

@@ -262,4 +262,6 @@
<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="building_bubble_title">We Are Building</string>
<string name="private_bubble">Your Private Bubble!</string>
</resources>

正在加载...
取消
保存