mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Add UI support for configuring a proxy.
This commit is contained in:
@@ -25,6 +25,7 @@ public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragm
|
||||
|
||||
private static final String TAG = Log.tag(DataAndStoragePreferenceFragment.class);
|
||||
private static final String MANAGE_STORAGE_KEY = "pref_data_manage";
|
||||
private static final String USE_PROXY_KEY = "pref_use_proxy";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
@@ -52,6 +53,12 @@ public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragm
|
||||
viewModel.getStorageBreakdown()
|
||||
.observe(requireActivity(),
|
||||
breakdown -> manageStorage.setSummary(Util.getPrettyFileSize(breakdown.getTotalSize())));
|
||||
|
||||
|
||||
findPreference(USE_PROXY_KEY).setOnPreferenceClickListener(unused -> {
|
||||
requireApplicationPreferencesActivity().pushFragment(EditProxyFragment.newInstance());
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,6 +72,7 @@ public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragm
|
||||
requireApplicationPreferencesActivity().getSupportActionBar().setTitle(R.string.preferences__data_and_storage);
|
||||
setMediaDownloadSummaries();
|
||||
ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(requireActivity()).refreshStorageBreakdown(requireContext());
|
||||
findPreference(USE_PROXY_KEY).setSummary(SignalStore.proxy().isProxyEnabled() ? R.string.preferences_on : R.string.preferences_off);
|
||||
}
|
||||
|
||||
private @NonNull ApplicationPreferencesActivity requireApplicationPreferencesActivity() {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.core.app.ShareCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||
|
||||
public class EditProxyFragment extends Fragment {
|
||||
|
||||
private SwitchCompat proxySwitch;
|
||||
private EditText proxyText;
|
||||
private TextView proxyTitle;
|
||||
private TextView proxyStatus;
|
||||
private View shareButton;
|
||||
private CircularProgressButton saveButton;
|
||||
private EditProxyViewModel viewModel;
|
||||
|
||||
public static EditProxyFragment newInstance() {
|
||||
return new EditProxyFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.edit_proxy_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.proxySwitch = view.findViewById(R.id.edit_proxy_switch);
|
||||
this.proxyTitle = view.findViewById(R.id.edit_proxy_address_title);
|
||||
this.proxyText = view.findViewById(R.id.edit_proxy_host);
|
||||
this.proxyStatus = view.findViewById(R.id.edit_proxy_status);
|
||||
this.saveButton = view.findViewById(R.id.edit_proxy_save);
|
||||
this.shareButton = view.findViewById(R.id.edit_proxy_share);
|
||||
|
||||
this.proxyText.setText(Optional.fromNullable(SignalStore.proxy().getProxy()).transform(SignalProxy::getHost).or(""));
|
||||
this.proxySwitch.setChecked(SignalStore.proxy().isProxyEnabled());
|
||||
|
||||
initViewModel();
|
||||
|
||||
saveButton.setOnClickListener(v -> onSaveClicked());
|
||||
shareButton.setOnClickListener(v -> onShareClicked());
|
||||
proxySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> viewModel.onToggleProxy(isChecked));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((ApplicationPreferencesActivity) requireActivity()).requireSupportActionBar().setTitle(R.string.preferences_use_proxy);
|
||||
}
|
||||
|
||||
private void initViewModel() {
|
||||
viewModel = ViewModelProviders.of(this).get(EditProxyViewModel.class);
|
||||
|
||||
viewModel.getUiState().observe(getViewLifecycleOwner(), this::presentUiState);
|
||||
viewModel.getProxyState().observe(getViewLifecycleOwner(), this::presentProxyState);
|
||||
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent);
|
||||
viewModel.getSaveState().observe(getViewLifecycleOwner(), this::presentSaveState);
|
||||
}
|
||||
|
||||
private void presentUiState(@NonNull EditProxyViewModel.UiState uiState) {
|
||||
switch (uiState) {
|
||||
case ALL_ENABLED:
|
||||
proxyText.setEnabled(true);
|
||||
proxyText.setAlpha(1);
|
||||
saveButton.setEnabled(true);
|
||||
saveButton.setAlpha(1);
|
||||
shareButton.setEnabled(true);
|
||||
shareButton.setAlpha(1);
|
||||
proxyTitle.setAlpha(1);
|
||||
proxyStatus.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case ALL_DISABLED:
|
||||
proxyText.setEnabled(false);
|
||||
proxyText.setAlpha(0.5f);
|
||||
saveButton.setEnabled(false);
|
||||
saveButton.setAlpha(0.5f);
|
||||
shareButton.setEnabled(false);
|
||||
shareButton.setAlpha(0.5f);
|
||||
proxyTitle.setAlpha(0.5f);
|
||||
proxyStatus.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void presentProxyState(@NonNull PipeConnectivityListener.State proxyState) {
|
||||
switch (proxyState) {
|
||||
case DISCONNECTED:
|
||||
case CONNECTING:
|
||||
proxyStatus.setText(R.string.preferences_connecting_to_proxy);
|
||||
proxyStatus.setTextColor(getResources().getColor(R.color.signal_text_secondary));
|
||||
break;
|
||||
case CONNECTED:
|
||||
proxyStatus.setText(R.string.preferences_connected_to_proxy);
|
||||
proxyStatus.setTextColor(getResources().getColor(R.color.signal_accent_green));
|
||||
break;
|
||||
case FAILURE:
|
||||
proxyStatus.setText(R.string.preferences_connection_failed);
|
||||
proxyStatus.setTextColor(getResources().getColor(R.color.signal_alert_primary));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void presentEvent(@NonNull EditProxyViewModel.Event event) {
|
||||
switch (event) {
|
||||
case PROXY_SUCCESS:
|
||||
proxyStatus.setVisibility(View.VISIBLE);
|
||||
proxyText.setText(Optional.fromNullable(SignalStore.proxy().getProxy()).transform(SignalProxy::getHost).or(""));
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.preferences_success)
|
||||
.setMessage(R.string.preferences_you_are_connected_to_the_proxy)
|
||||
.setPositiveButton(android.R.string.ok, (d, i) -> d.dismiss())
|
||||
.show();
|
||||
break;
|
||||
case PROXY_FAILURE:
|
||||
proxyStatus.setVisibility(View.GONE);
|
||||
proxyText.setText(Optional.fromNullable(SignalStore.proxy().getProxy()).transform(SignalProxy::getHost).or(""));
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.preferences_failed_to_connect)
|
||||
.setMessage(R.string.preferences_couldnt_connect_to_the_proxy)
|
||||
.setPositiveButton(android.R.string.ok, (d, i) -> d.dismiss())
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void presentSaveState(@NonNull EditProxyViewModel.SaveState state) {
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
saveButton.setClickable(true);
|
||||
saveButton.setIndeterminateProgressMode(false);
|
||||
saveButton.setProgress(0);
|
||||
break;
|
||||
case IN_PROGRESS:
|
||||
saveButton.setClickable(false);
|
||||
saveButton.setIndeterminateProgressMode(true);
|
||||
saveButton.setProgress(50);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onSaveClicked() {
|
||||
viewModel.onSaveClicked(proxyText.getText().toString());
|
||||
}
|
||||
|
||||
private void onShareClicked() {
|
||||
String host = proxyText.getText().toString();
|
||||
ShareCompat.IntentBuilder.from(requireActivity())
|
||||
.setText(host)
|
||||
.setType("text/plain")
|
||||
.startChooser();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||
import org.thoughtcrime.securesms.util.SignalProxyUtil;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class EditProxyViewModel extends ViewModel {
|
||||
|
||||
private final SingleLiveEvent<Event> events;
|
||||
private final MutableLiveData<UiState> uiState;
|
||||
private final MutableLiveData<SaveState> saveState;
|
||||
|
||||
public EditProxyViewModel() {
|
||||
this.events = new SingleLiveEvent<>();
|
||||
this.uiState = new MutableLiveData<>();
|
||||
this.saveState = new MutableLiveData<>(SaveState.IDLE);
|
||||
|
||||
if (SignalStore.proxy().isProxyEnabled()) {
|
||||
uiState.setValue(UiState.ALL_ENABLED);
|
||||
} else {
|
||||
uiState.setValue(UiState.ALL_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void onToggleProxy(boolean enabled) {
|
||||
if (enabled) {
|
||||
SignalProxy currentProxy = SignalStore.proxy().getProxy();
|
||||
|
||||
if (currentProxy != null) {
|
||||
SignalProxyUtil.enableProxy(currentProxy);
|
||||
}
|
||||
uiState.postValue(UiState.ALL_ENABLED);
|
||||
} else {
|
||||
SignalProxyUtil.disableProxy();
|
||||
uiState.postValue(UiState.ALL_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSaveClicked(@NonNull String host) {
|
||||
String parsedHost = SignalProxyUtil.parseHostFromProxyLink(host);
|
||||
String trueHost = parsedHost != null ? parsedHost : host;
|
||||
|
||||
saveState.postValue(SaveState.IN_PROGRESS);
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
SignalProxyUtil.enableProxy(new SignalProxy(trueHost, 443));
|
||||
|
||||
boolean success = SignalProxyUtil.testWebsocketConnection(TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
if (success) {
|
||||
events.postValue(Event.PROXY_SUCCESS);
|
||||
} else {
|
||||
SignalProxyUtil.disableProxy();
|
||||
events.postValue(Event.PROXY_FAILURE);
|
||||
}
|
||||
|
||||
saveState.postValue(SaveState.IDLE);
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull LiveData<UiState> getUiState() {
|
||||
return uiState;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<Event> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
@NonNull LiveData<PipeConnectivityListener.State> getProxyState() {
|
||||
return ApplicationDependencies.getPipeListener().getState();
|
||||
}
|
||||
|
||||
public @NonNull LiveData<SaveState> getSaveState() {
|
||||
return saveState;
|
||||
}
|
||||
|
||||
enum UiState {
|
||||
ALL_DISABLED, ALL_ENABLED
|
||||
}
|
||||
|
||||
public enum Event {
|
||||
PROXY_SUCCESS, PROXY_FAILURE
|
||||
}
|
||||
|
||||
public enum SaveState {
|
||||
IDLE, IN_PROGRESS
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user