mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 06:09:55 +01:00
Improve UI for shard return
This commit is contained in:
@@ -87,6 +87,7 @@ import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
|
|||||||
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
|
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity;
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardFragment;
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardSuccessFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
|
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
|
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardFragment;
|
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardFragment;
|
||||||
@@ -293,4 +294,6 @@ public interface ActivityComponent {
|
|||||||
void inject(CustodianReturnShardFragment custodianReturnShardFragment);
|
void inject(CustodianReturnShardFragment custodianReturnShardFragment);
|
||||||
|
|
||||||
void inject(OwnerReturnShardFragment ownerReturnShardFragment);
|
void inject(OwnerReturnShardFragment ownerReturnShardFragment);
|
||||||
|
|
||||||
|
void inject(CustodianReturnShardSuccessFragment custodianReturnShardSuccessFragment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class DistributedBackupActivity extends BriarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void contactsSelected(Collection<ContactId> contacts) {
|
public void contactsSelected(Collection<ContactId> contacts) {
|
||||||
Toast.makeText(this,
|
Toast.makeText(this,
|
||||||
String.format("selected %d contacts", contacts.size()),
|
String.format("Selected %d contacts", contacts.size()),
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
custodians = contacts;
|
custodians = contacts;
|
||||||
ThresholdSelectorFragment fragment =
|
ThresholdSelectorFragment fragment =
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.briarproject.briar.android.activity.ActivityComponent;
|
|||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.CustodianRecoveryModeExplainerFragment;
|
import org.briarproject.briar.android.socialbackup.CustodianRecoveryModeExplainerFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
|
||||||
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -59,13 +60,17 @@ public class CustodianReturnShardActivity extends BriarActivity
|
|||||||
viewModel.getShowCameraFragment().observeEvent(this, show -> {
|
viewModel.getShowCameraFragment().observeEvent(this, show -> {
|
||||||
if (show) showCameraFragment();
|
if (show) showCameraFragment();
|
||||||
});
|
});
|
||||||
|
viewModel.getSuccessDismissed().observeEvent(this, dismissed -> {
|
||||||
|
if (dismissed) finish();
|
||||||
|
});
|
||||||
viewModel.getState()
|
viewModel.getState()
|
||||||
.observe(this, this::onReturnShardStateChanged);
|
.observe(this, this::onReturnShardStateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReturnShardStateChanged(CustodianTask.State state) {
|
private void onReturnShardStateChanged(CustodianTask.State state) {
|
||||||
if (state instanceof CustodianTask.State.Success) {
|
if (state instanceof CustodianTask.State.Success) {
|
||||||
|
CustodianReturnShardSuccessFragment fragment = new CustodianReturnShardSuccessFragment();
|
||||||
|
showNextFragment(fragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class CustodianReturnShardSuccessFragment extends
|
||||||
|
BaseFragment {
|
||||||
|
|
||||||
|
public static final String TAG =
|
||||||
|
CustodianReturnShardFragment.class.getName();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private CustodianReturnShardViewModel viewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(CustodianReturnShardViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_recovery_custodian_done,
|
||||||
|
container, false);
|
||||||
|
|
||||||
|
Button button = view.findViewById(R.id.button);
|
||||||
|
button.setOnClickListener(e -> viewModel.onSuccessDismissed());
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,8 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
|
|||||||
private boolean qrCodeRead = false;
|
private boolean qrCodeRead = false;
|
||||||
private final MutableLiveEvent<Boolean> showCameraFragment =
|
private final MutableLiveEvent<Boolean> showCameraFragment =
|
||||||
new MutableLiveEvent<>();
|
new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveEvent<Boolean> successDismissed =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
private final MutableLiveData<CustodianTask.State> state =
|
private final MutableLiveData<CustodianTask.State> state =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
final QrCodeDecoder qrCodeDecoder;
|
final QrCodeDecoder qrCodeDecoder;
|
||||||
@@ -100,6 +102,12 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
|
|||||||
showCameraFragment.setEvent(true);
|
showCameraFragment.setEvent(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public void onSuccessDismissed() {
|
||||||
|
successDismissed.setEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QrCodeDecoder getQrCodeDecoder() {
|
QrCodeDecoder getQrCodeDecoder() {
|
||||||
return qrCodeDecoder;
|
return qrCodeDecoder;
|
||||||
}
|
}
|
||||||
@@ -108,6 +116,9 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
|
|||||||
return showCameraFragment;
|
return showCameraFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveEvent<Boolean> getSuccessDismissed() {
|
||||||
|
return successDismissed;
|
||||||
|
}
|
||||||
LiveData<CustodianTask.State> getState() {
|
LiveData<CustodianTask.State> getState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactErrorFragment;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||||
|
|
||||||
@@ -23,7 +22,6 @@ import androidx.fragment.app.FragmentManager;
|
|||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -78,10 +76,6 @@ public class OwnerReturnShardActivity extends BaseActivity
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
showInitialFragment(new OwnerRecoveryModeExplainerFragment());
|
showInitialFragment(new OwnerRecoveryModeExplainerFragment());
|
||||||
}
|
}
|
||||||
// viewModel.getCheckPermissions().observeEvent(this, check ->
|
|
||||||
// permissionManager.checkPermissions());
|
|
||||||
// viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r ->
|
|
||||||
// requestBluetoothDiscoverable()); // never false
|
|
||||||
viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
|
viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
|
||||||
if (show) showQrCodeFragment();
|
if (show) showQrCodeFragment();
|
||||||
});
|
});
|
||||||
@@ -117,6 +111,7 @@ public class OwnerReturnShardActivity extends BaseActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
|
// TODO should we cancel the return shard task here?
|
||||||
if (viewModel.getState()
|
if (viewModel.getState()
|
||||||
.getValue() instanceof SecretOwnerTask.State.Failure) {
|
.getValue() instanceof SecretOwnerTask.State.Failure) {
|
||||||
// re-create this activity when going back in failed state
|
// re-create this activity when going back in failed state
|
||||||
@@ -141,47 +136,23 @@ public class OwnerReturnShardActivity extends BaseActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onReturnShardStateChanged(SecretOwnerTask.State state) {
|
private void onReturnShardStateChanged(SecretOwnerTask.State state) {
|
||||||
// if (state instanceof ReturnShardState.SocialBackupExchangeFinished) {
|
if (state instanceof SecretOwnerTask.State.Success) {
|
||||||
// ReturnShardState.SocialBackupExchangeResult result =
|
Toast.makeText(this,
|
||||||
// ((ReturnShardState.SocialBackupExchangeFinished) state).result;
|
"Success - got shard",
|
||||||
// onSocialBackupExchangeResult(result);
|
Toast.LENGTH_SHORT).show();
|
||||||
// } else if (state instanceof ReturnShardState.Failed) {
|
finish();
|
||||||
// Boolean qrCodeTooOld =
|
} else if (state instanceof SecretOwnerTask.State.Failure) {
|
||||||
// ((ReturnShardState.Failed) state).qrCodeTooOld;
|
Toast.makeText(this,
|
||||||
// onAddingContactFailed(qrCodeTooOld);
|
"Shard return failed!",
|
||||||
// }
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
finish();
|
||||||
|
|
||||||
private void onSocialBackupExchangeResult(
|
|
||||||
ReturnShardState.SocialBackupExchangeResult result) {
|
|
||||||
if (result instanceof ReturnShardState.SocialBackupExchangeResult.Success) {
|
|
||||||
// String text = getString(R.string.contact_added_toast, contactName);
|
|
||||||
Toast.makeText(this, "Shard return successful", LENGTH_LONG).show();
|
|
||||||
supportFinishAfterTransition();
|
|
||||||
} else if (result instanceof ReturnShardState.SocialBackupExchangeResult.Error) {
|
|
||||||
showErrorFragment();
|
|
||||||
} else throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAddingContactFailed(@Nullable Boolean qrCodeTooOld) {
|
|
||||||
if (qrCodeTooOld == null) {
|
|
||||||
showErrorFragment();
|
|
||||||
} else {
|
|
||||||
String msg;
|
|
||||||
if (qrCodeTooOld) {
|
|
||||||
msg = getString(R.string.qr_code_too_old,
|
|
||||||
getString(R.string.app_name));
|
|
||||||
} else {
|
|
||||||
msg = getString(R.string.qr_code_too_new,
|
|
||||||
getString(R.string.app_name));
|
|
||||||
}
|
|
||||||
showNextFragment(AddNearbyContactErrorFragment.newInstance(msg));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showErrorFragment() {
|
// private void showErrorFragment() {
|
||||||
showNextFragment(new AddNearbyContactErrorFragment());
|
// // TODO change this for an appropriate error message fragment
|
||||||
}
|
// showNextFragment(new AddNearbyContactErrorFragment());
|
||||||
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
|||||||
@@ -136,91 +136,12 @@ class OwnerReturnShardViewModel extends AndroidViewModel implements SecretOwnerT
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void stopListening() {
|
private void stopListening() {
|
||||||
task.cancel();
|
ioExecutor.execute(() -> {
|
||||||
// KeyAgreementTask oldTask = task;
|
task.cancel();
|
||||||
// ioExecutor.execute(() -> {
|
});
|
||||||
// if (oldTask != null) oldTask.stopListening();
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void eventOccurred(Event e) {
|
|
||||||
// if (e instanceof TransportStateEvent) {
|
|
||||||
// TransportStateEvent t = (TransportStateEvent) e;
|
|
||||||
// if (t.getTransportId().equals(BluetoothConstants.ID)) {
|
|
||||||
// if (LOG.isLoggable(INFO)) {
|
|
||||||
// LOG.info("Bluetooth state changed to " + t.getState());
|
|
||||||
// }
|
|
||||||
// showQrCodeFragmentIfAllowed();
|
|
||||||
// } else if (t.getTransportId().equals(LanTcpConstants.ID)) {
|
|
||||||
// if (LOG.isLoggable(INFO)) {
|
|
||||||
// LOG.info("Wifi state changed to " + t.getState());
|
|
||||||
// }
|
|
||||||
// showQrCodeFragmentIfAllowed();
|
|
||||||
// }
|
|
||||||
// } else if (e instanceof KeyAgreementListeningEvent) {
|
|
||||||
// LOG.info("KeyAgreementListeningEvent received");
|
|
||||||
// KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e;
|
|
||||||
// onLocalPayloadReceived(event.getLocalPayload());
|
|
||||||
// } else if (e instanceof KeyAgreementWaitingEvent) {
|
|
||||||
// LOG.info("KeyAgreementWaitingEvent received");
|
|
||||||
// state.setValue(new ReturnShardState.KeyAgreementWaiting());
|
|
||||||
// } else if (e instanceof KeyAgreementStartedEvent) {
|
|
||||||
// LOG.info("KeyAgreementStartedEvent received");
|
|
||||||
// state.setValue(new ReturnShardState.KeyAgreementStarted());
|
|
||||||
// } else if (e instanceof KeyAgreementFinishedEvent) {
|
|
||||||
// LOG.info("KeyAgreementFinishedEvent received");
|
|
||||||
// KeyAgreementResult result =
|
|
||||||
// ((KeyAgreementFinishedEvent) e).getResult();
|
|
||||||
// startContactExchange(result);
|
|
||||||
// state.setValue(new ReturnShardState.SocialBackupExchangeStarted());
|
|
||||||
// } else if (e instanceof KeyAgreementAbortedEvent) {
|
|
||||||
// LOG.info("KeyAgreementAbortedEvent received");
|
|
||||||
// resetPayloadFlags();
|
|
||||||
// state.setValue(new ReturnShardState.Failed());
|
|
||||||
// } else if (e instanceof KeyAgreementFailedEvent) {
|
|
||||||
// LOG.info("KeyAgreementFailedEvent received");
|
|
||||||
// resetPayloadFlags();
|
|
||||||
// state.setValue(new ReturnShardState.Failed());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
// private void startContactExchange(KeyAgreementResult result) {
|
|
||||||
// TransportId t = result.getTransportId();
|
|
||||||
// DuplexTransportConnection conn = result.getConnection();
|
|
||||||
// SecretKey masterKey = result.getMasterKey();
|
|
||||||
// boolean alice = result.wasAlice();
|
|
||||||
// ioExecutor.execute(() -> {
|
|
||||||
// try {
|
|
||||||
// if (sending) {
|
|
||||||
// socialBackupExchangeManager.sendReturnShard(conn, masterKey, alice, returnShardPayload);
|
|
||||||
// } else {
|
|
||||||
// returnShardPayload = socialBackupExchangeManager.receiveReturnShard(conn, masterKey, alice);
|
|
||||||
// }
|
|
||||||
// ReturnShardState.SocialBackupExchangeResult.Success
|
|
||||||
// success =
|
|
||||||
// new ReturnShardState.SocialBackupExchangeResult.Success();
|
|
||||||
// state.postValue(
|
|
||||||
// new ReturnShardState.SocialBackupExchangeFinished(success));
|
|
||||||
// } catch (ContactExistsException e) {
|
|
||||||
// tryToClose(conn);
|
|
||||||
// ReturnShardState.SocialBackupExchangeResult.Error
|
|
||||||
// error = new ReturnShardState.SocialBackupExchangeResult.Error(
|
|
||||||
// e.getRemoteAuthor());
|
|
||||||
// state.postValue(
|
|
||||||
// new ReturnShardState.SocialBackupExchangeFinished(error));
|
|
||||||
// } catch (DbException | IOException e) {
|
|
||||||
// tryToClose(conn);
|
|
||||||
// logException(LOG, WARNING, e);
|
|
||||||
// ReturnShardState.SocialBackupExchangeResult.Error
|
|
||||||
// error =
|
|
||||||
// new ReturnShardState.SocialBackupExchangeResult.Error(null);
|
|
||||||
// state.postValue(
|
|
||||||
// new ReturnShardState.SocialBackupExchangeFinished(error));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,18 +173,19 @@ class OwnerReturnShardViewModel extends AndroidViewModel implements SecretOwnerT
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStateChanged(SecretOwnerTask.State state) {
|
public void onStateChanged(SecretOwnerTask.State state) {
|
||||||
if (state instanceof SecretOwnerTask.State.Listening) {
|
this.state.postValue(state);
|
||||||
DisplayMetrics dm = getApplication().getResources().getDisplayMetrics();
|
if (state instanceof SecretOwnerTask.State.Listening) {
|
||||||
ioExecutor.execute(() -> {
|
DisplayMetrics dm = getApplication().getResources().getDisplayMetrics();
|
||||||
byte[] payloadBytes = ((SecretOwnerTask.State.Listening) state).getLocalPayload();
|
ioExecutor.execute(() -> {
|
||||||
if (LOG.isLoggable(INFO)) {
|
byte[] payloadBytes = ((SecretOwnerTask.State.Listening) state).getLocalPayload();
|
||||||
LOG.info("Local payload is " + payloadBytes.length
|
if (LOG.isLoggable(INFO)) {
|
||||||
+ " bytes");
|
LOG.info("Local payload is " + payloadBytes.length
|
||||||
}
|
+ " bytes");
|
||||||
// Use ISO 8859-1 to encode bytes directly as a string
|
}
|
||||||
String content = new String(payloadBytes, ISO_8859_1);
|
// Use ISO 8859-1 to encode bytes directly as a string
|
||||||
qrCodeBitmap = QrCodeUtils.createQrCode(dm, content);
|
String content = new String(payloadBytes, ISO_8859_1);
|
||||||
});
|
qrCodeBitmap = QrCodeUtils.createQrCode(dm, content);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,17 @@
|
|||||||
android:paddingRight="@dimen/margin_large"
|
android:paddingRight="@dimen/margin_large"
|
||||||
android:paddingBottom="@dimen/margin_medium">
|
android:paddingBottom="@dimen/margin_medium">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/backup_done_dismiss"
|
||||||
|
android:textSize="24sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textView2" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView2"
|
android:id="@+id/textView2"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
Reference in New Issue
Block a user