diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 766f10a655..c6e73dc98d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -570,6 +570,10 @@
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize" />
+
+
diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java
index 8850a198a6..21e0849db7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java
@@ -20,17 +20,20 @@ import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
+import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
+import org.thoughtcrime.securesms.profiles.username.AddAUsernameActivity;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.AppStartup;
+import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Locale;
@@ -52,6 +55,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
private static final int STATE_TRANSFER_ONGOING = 8;
private static final int STATE_TRANSFER_LOCKED = 9;
private static final int STATE_CHANGE_NUMBER_LOCK = 10;
+ private static final int STATE_CREATE_USERNAME = 11;
private SignalServiceNetworkAccess networkAccess;
private BroadcastReceiver clearKeyReceiver;
@@ -156,6 +160,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
case STATE_TRANSFER_ONGOING: return getOldDeviceTransferIntent();
case STATE_TRANSFER_LOCKED: return getOldDeviceTransferLockedIntent();
case STATE_CHANGE_NUMBER_LOCK: return getChangeNumberLockIntent();
+ case STATE_CREATE_USERNAME: return getCreateUsernameIntent();
default: return null;
}
}
@@ -175,6 +180,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
return STATE_CREATE_SIGNAL_PIN;
} else if (userMustSetProfileName()) {
return STATE_CREATE_PROFILE_NAME;
+ } else if (shouldAskUserToCreateUsername()) {
+ return STATE_CREATE_USERNAME;
} else if (userMustCreateSignalPin()) {
return STATE_CREATE_SIGNAL_PIN;
} else if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null && getClass() != OldDeviceTransferActivity.class) {
@@ -200,6 +207,13 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
return !SignalStore.registrationValues().isRegistrationComplete() && Recipient.self().getProfileName().isEmpty();
}
+ private boolean shouldAskUserToCreateUsername() {
+ return FeatureFlags.usernames() &&
+ FeatureFlags.phoneNumberPrivacy() &&
+ !SignalStore.uiHints().hasSetOrSkippedUsernameCreation() &&
+ SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode() == PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED;
+ }
+
private Intent getCreatePassphraseIntent() {
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
}
@@ -259,6 +273,10 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
return ChangeNumberLockActivity.createIntent(this);
}
+ private Intent getCreateUsernameIntent() {
+ return getRoutedIntent(AddAUsernameActivity.class, getIntent());
+ }
+
private Intent getRoutedIntent(Intent destination, @Nullable Intent nextIntent) {
if (nextIntent != null) destination.putExtra("next_intent", nextIntent);
return destination;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProgressCard.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProgressCard.kt
new file mode 100644
index 0000000000..892d3f717e
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProgressCard.kt
@@ -0,0 +1,22 @@
+package org.thoughtcrime.securesms.components
+
+import android.content.Context
+import android.util.AttributeSet
+import com.google.android.material.card.MaterialCardView
+import org.thoughtcrime.securesms.R
+
+/**
+ * A small card with a circular progress indicator in it. Usable in place
+ * of a ProgressDialog, which is deprecated.
+ *
+ * Remember to add this as the last UI element in your XML hierarchy so it'll
+ * draw over top of other elements.
+ */
+class ProgressCard @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : MaterialCardView(context, attrs) {
+ init {
+ inflate(context, R.layout.progress_card, this)
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHints.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHints.java
index 7976b84af6..bf87fda54c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHints.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHints.java
@@ -9,6 +9,7 @@ public class UiHints extends SignalStoreValues {
private static final String HAS_SEEN_GROUP_SETTINGS_MENU_TOAST = "uihints.has_seen_group_settings_menu_toast";
private static final String HAS_CONFIRMED_DELETE_FOR_EVERYONE_ONCE = "uihints.has_confirmed_delete_for_everyone_once";
+ private static final String HAS_SET_OR_SKIPPED_USERNAME_CREATION = "uihints.has_set_or_skipped_username_creation";
UiHints(@NonNull KeyValueStore store) {
super(store);
@@ -39,4 +40,12 @@ public class UiHints extends SignalStoreValues {
public boolean hasConfirmedDeleteForEveryoneOnce() {
return getBoolean(HAS_CONFIRMED_DELETE_FOR_EVERYONE_ONCE, false);
}
+
+ public boolean hasSetOrSkippedUsernameCreation() {
+ return getBoolean(HAS_SET_OR_SKIPPED_USERNAME_CREATION, false);
+ }
+
+ public void markHasSetOrSkippedUsernameCreation() {
+ putBoolean(HAS_SET_OR_SKIPPED_USERNAME_CREATION, true);
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileActivity.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileActivity.java
index 9c937af1d5..edf12ea040 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileActivity.java
@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.profiles.edit;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -20,7 +19,6 @@ import org.thoughtcrime.securesms.util.DynamicTheme;
/**
* Shows editing screen for your profile during registration. Also handles group name editing.
*/
-@SuppressLint("StaticFieldLeak")
public class EditProfileActivity extends BaseActivity implements EditProfileFragment.Controller {
public static final String NEXT_INTENT = "next_intent";
@@ -37,13 +35,6 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
return intent;
}
- public static @NonNull Intent getIntentForUserProfileEdit(@NonNull Context context) {
- Intent intent = new Intent(context, EditProfileActivity.class);
- intent.putExtra(EditProfileActivity.EXCLUDE_SYSTEM, true);
- intent.putExtra(EditProfileActivity.NEXT_BUTTON_TEXT, R.string.save);
- return intent;
- }
-
public static @NonNull Intent getIntentForGroupProfile(@NonNull Context context, @NonNull GroupId groupId) {
Intent intent = new Intent(context, EditProfileActivity.class);
intent.putExtra(EditProfileActivity.SHOW_TOOLBAR, true);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java
index 08eaf978cd..9456fcc72f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java
@@ -18,6 +18,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
@@ -91,7 +92,7 @@ public class EditProfileFragment extends LoggingFragment {
GroupId groupId = GroupId.parseNullableOrThrow(requireArguments().getString(GROUP_ID, null));
initializeViewModel(requireArguments().getBoolean(EXCLUDE_SYSTEM, false), groupId, savedInstanceState != null);
- initializeResources(view, groupId);
+ initializeResources(groupId);
initializeProfileAvatar();
initializeProfileName();
@@ -151,11 +152,10 @@ public class EditProfileFragment extends LoggingFragment {
EditProfileViewModel.Factory factory = new EditProfileViewModel.Factory(repository, hasSavedInstanceState, groupId);
- viewModel = ViewModelProviders.of(requireActivity(), factory)
- .get(EditProfileViewModel.class);
+ viewModel = new ViewModelProvider(requireActivity(), factory).get(EditProfileViewModel.class);
}
- private void initializeResources(@NonNull View view, @Nullable GroupId groupId) {
+ private void initializeResources(@Nullable GroupId groupId) {
Bundle arguments = requireArguments();
boolean isEditingGroup = groupId != null;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java
index bb36abf75f..8d2ff82199 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java
@@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.profiles.manage;
+import android.content.Intent;
import android.content.res.ColorStateList;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -17,6 +17,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
+import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
@@ -29,10 +30,12 @@ import com.google.android.material.textfield.TextInputLayout;
import org.signal.core.util.DimensionUnit;
import org.thoughtcrime.securesms.LoggingFragment;
+import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.databinding.UsernameEditFragmentBinding;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.util.AccessibilityUtil;
import org.thoughtcrime.securesms.util.FragmentResultContract;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.UsernameUtil;
@@ -49,6 +52,7 @@ public class UsernameEditFragment extends LoggingFragment {
private UsernameEditFragmentBinding binding;
private ImageView suffixProgress;
private LifecycleDisposable lifecycleDisposable;
+ private UsernameEditFragmentArgs args;
public static UsernameEditFragment newInstance() {
return new UsernameEditFragment();
@@ -62,20 +66,37 @@ public class UsernameEditFragment extends LoggingFragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- binding.toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).popBackStack());
+ Bundle bundle = getArguments();
+ if (bundle != null) {
+ args = UsernameEditFragmentArgs.fromBundle(bundle);
+ } else {
+ args = new UsernameEditFragmentArgs.Builder().build();
+ }
+
+ if (args.getIsInRegistration()) {
+ binding.toolbar.setNavigationIcon(null);
+ binding.toolbar.setTitle(R.string.UsernameEditFragment__add_a_username);
+ binding.usernameSkipButton.setVisibility(View.VISIBLE);
+ binding.usernameDoneButton.setVisibility(View.VISIBLE);
+ } else {
+ binding.toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).popBackStack());
+ binding.usernameSubmitButton.setVisibility(View.VISIBLE);
+ }
binding.usernameTextWrapper.setErrorIconDrawable(null);
lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.bindTo(getViewLifecycleOwner());
- viewModel = new ViewModelProvider(this, new UsernameEditViewModel.Factory()).get(UsernameEditViewModel.class);
+ viewModel = new ViewModelProvider(this, new UsernameEditViewModel.Factory(args.getIsInRegistration())).get(UsernameEditViewModel.class);
lifecycleDisposable.add(viewModel.getUiState().subscribe(this::onUiStateChanged));
viewModel.getEvents().observe(getViewLifecycleOwner(), this::onEvent);
binding.usernameSubmitButton.setOnClickListener(v -> viewModel.onUsernameSubmitted());
binding.usernameDeleteButton.setOnClickListener(v -> viewModel.onUsernameDeleted());
+ binding.usernameDoneButton.setOnClickListener(v -> viewModel.onUsernameSubmitted());
+ binding.usernameSkipButton.setOnClickListener(v -> viewModel.onUsernameSkipped());
UsernameState usernameState = Recipient.self().getUsername().map(UsernameState.Set::new).orElse(UsernameState.NoUsername.INSTANCE);
binding.usernameText.setText(usernameState.getNickname());
@@ -142,15 +163,82 @@ public class UsernameEditFragment extends LoggingFragment {
}
private void onUiStateChanged(@NonNull UsernameEditViewModel.State state) {
- EditText usernameInput = binding.usernameText;
+ TextInputLayout usernameInputWrapper = binding.usernameTextWrapper;
+
+ presentSuffix(state.getUsername());
+ presentButtonState(state.getButtonState());
+
+ switch (state.getUsernameStatus()) {
+ case NONE:
+ usernameInputWrapper.setError(null);
+ break;
+ case TOO_SHORT:
+ case TOO_LONG:
+ usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_must_be_between_a_and_b_characters, UsernameUtil.MIN_LENGTH, UsernameUtil.MAX_LENGTH));
+ usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
+
+ break;
+ case INVALID_CHARACTERS:
+ usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_can_only_include));
+ usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
+
+ break;
+ case CANNOT_START_WITH_NUMBER:
+ usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_cannot_begin_with_a_number));
+ usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
+
+ break;
+ case INVALID_GENERIC:
+ usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_username_is_invalid));
+ usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
+
+ break;
+ case TAKEN:
+ usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_this_username_is_taken));
+ usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
+
+ break;
+ }
+ }
+
+ private void presentButtonState(@NonNull UsernameEditViewModel.ButtonState buttonState) {
+ if (args.getIsInRegistration()) {
+ presentRegistrationButtonState(buttonState);
+ } else {
+ presentProfileUpdateButtonState(buttonState);
+ }
+ }
+
+ private void presentRegistrationButtonState(@NonNull UsernameEditViewModel.ButtonState buttonState) {
+ binding.usernameText.setEnabled(true);
+ binding.usernameProgressCard.setVisibility(View.GONE);
+
+ switch (buttonState) {
+ case SUBMIT:
+ binding.usernameDoneButton.setEnabled(true);
+ binding.usernameDoneButton.setAlpha(1f);
+ break;
+ case SUBMIT_DISABLED:
+ binding.usernameDoneButton.setEnabled(false);
+ binding.usernameDoneButton.setAlpha(DISABLED_ALPHA);
+ break;
+ case SUBMIT_LOADING:
+ binding.usernameDoneButton.setEnabled(false);
+ binding.usernameDoneButton.setAlpha(DISABLED_ALPHA);
+ binding.usernameProgressCard.setVisibility(View.VISIBLE);
+ break;
+ default:
+ throw new IllegalStateException("Delete functionality is not available during registration.");
+ }
+ }
+
+ private void presentProfileUpdateButtonState(@NonNull UsernameEditViewModel.ButtonState buttonState) {
CircularProgressMaterialButton submitButton = binding.usernameSubmitButton;
CircularProgressMaterialButton deleteButton = binding.usernameDeleteButton;
- TextInputLayout usernameInputWrapper = binding.usernameTextWrapper;
+ EditText usernameInput = binding.usernameText;
usernameInput.setEnabled(true);
- presentSuffix(state.getUsername());
-
- switch (state.getButtonState()) {
+ switch (buttonState) {
case SUBMIT:
submitButton.cancelSpinning();
submitButton.setVisibility(View.VISIBLE);
@@ -194,38 +282,6 @@ public class UsernameEditFragment extends LoggingFragment {
usernameInput.setEnabled(false);
break;
}
-
- switch (state.getUsernameStatus()) {
- case NONE:
- usernameInputWrapper.setError(null);
- break;
- case TOO_SHORT:
- case TOO_LONG:
- usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_must_be_between_a_and_b_characters, UsernameUtil.MIN_LENGTH, UsernameUtil.MAX_LENGTH));
- usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
-
- break;
- case INVALID_CHARACTERS:
- usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_can_only_include));
- usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
-
- break;
- case CANNOT_START_WITH_NUMBER:
- usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_cannot_begin_with_a_number));
- usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
-
- break;
- case INVALID_GENERIC:
- usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_username_is_invalid));
- usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
-
- break;
- case TAKEN:
- usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_this_username_is_taken));
- usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
-
- break;
- }
}
private void presentSuffix(@NonNull UsernameState usernameState) {
@@ -257,7 +313,7 @@ public class UsernameEditFragment extends LoggingFragment {
switch (event) {
case SUBMIT_SUCCESS:
ResultContract.setUsernameCreated(getParentFragmentManager());
- NavHostFragment.findNavController(this).popBackStack();
+ closeScreen();
break;
case SUBMIT_FAIL_TAKEN:
Toast.makeText(requireContext(), R.string.UsernameEditFragment_this_username_is_taken, Toast.LENGTH_SHORT).show();
@@ -272,6 +328,36 @@ public class UsernameEditFragment extends LoggingFragment {
case NETWORK_FAILURE:
Toast.makeText(requireContext(), R.string.UsernameEditFragment_encountered_a_network_error, Toast.LENGTH_SHORT).show();
break;
+ case SKIPPED:
+ closeScreen();
+ break;
+ }
+ }
+
+ private void closeScreen() {
+ if (args.getIsInRegistration()) {
+ finishAndStartNextIntent();
+ } else {
+ NavHostFragment.findNavController(this).popBackStack();
+ }
+ }
+
+ private void finishAndStartNextIntent() {
+ FragmentActivity activity = requireActivity();
+ boolean didLaunch = false;
+ Intent activityIntent = activity.getIntent();
+
+ if (activityIntent != null) {
+ Intent nextIntent = activityIntent.getParcelableExtra(PassphraseRequiredActivity.NEXT_INTENT_EXTRA);
+ if (nextIntent != null) {
+ activity.startActivity(nextIntent);
+ activity.finish();
+ didLaunch = true;
+ }
+ }
+
+ if (!didLaunch) {
+ activity.finish();
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java
index 5aa103b7cf..231c54590a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java
@@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.ThreadUtil;
+import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.UsernameUtil;
@@ -45,13 +46,15 @@ class UsernameEditViewModel extends ViewModel {
private final RxStore uiState;
private final PublishProcessor nicknamePublisher;
private final CompositeDisposable disposables;
+ private final boolean isInRegistration;
- private UsernameEditViewModel() {
+ private UsernameEditViewModel(boolean isInRegistration) {
this.repo = new UsernameEditRepository();
this.uiState = new RxStore<>(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, Recipient.self().getUsername().map(UsernameState.Set::new).orElse(UsernameState.NoUsername.INSTANCE)), Schedulers.computation());
this.events = new SingleLiveEvent<>();
this.nicknamePublisher = PublishProcessor.create();
this.disposables = new CompositeDisposable();
+ this.isInRegistration = isInRegistration;
Disposable disposable = nicknamePublisher.debounce(NICKNAME_PUBLISHER_DEBOUNCE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
.subscribe(this::onNicknameChanged);
@@ -67,7 +70,7 @@ class UsernameEditViewModel extends ViewModel {
void onNicknameUpdated(@NonNull String nickname) {
uiState.update(state -> {
if (TextUtils.isEmpty(nickname) && Recipient.self().getUsername().isPresent()) {
- return new State(ButtonState.DELETE, UsernameStatus.NONE, UsernameState.NoUsername.INSTANCE);
+ return new State(isInRegistration ? ButtonState.SUBMIT_DISABLED : ButtonState.DELETE, UsernameStatus.NONE, UsernameState.NoUsername.INSTANCE);
}
Optional invalidReason = UsernameUtil.checkUsername(nickname);
@@ -79,6 +82,11 @@ class UsernameEditViewModel extends ViewModel {
nicknamePublisher.onNext(nickname);
}
+ void onUsernameSkipped() {
+ SignalStore.uiHints().markHasSetOrSkippedUsernameCreation();
+ events.setValue(Event.SKIPPED);
+ }
+
void onUsernameSubmitted() {
UsernameState usernameState = uiState.getState().getUsername();
@@ -107,6 +115,7 @@ class UsernameEditViewModel extends ViewModel {
switch (result) {
case SUCCESS:
+ SignalStore.uiHints().markHasSetOrSkippedUsernameCreation();
uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, state.usernameState));
events.postValue(Event.SUBMIT_SUCCESS);
break;
@@ -248,14 +257,21 @@ class UsernameEditViewModel extends ViewModel {
}
enum Event {
- NETWORK_FAILURE, SUBMIT_SUCCESS, DELETE_SUCCESS, SUBMIT_FAIL_INVALID, SUBMIT_FAIL_TAKEN
+ NETWORK_FAILURE, SUBMIT_SUCCESS, DELETE_SUCCESS, SUBMIT_FAIL_INVALID, SUBMIT_FAIL_TAKEN, SKIPPED
}
static class Factory extends ViewModelProvider.NewInstanceFactory {
+
+ private final boolean isInRegistration;
+
+ Factory(boolean isInRegistration) {
+ this.isInRegistration = isInRegistration;
+ }
+
@Override
public @NonNull T create(@NonNull Class modelClass) {
//noinspection ConstantConditions
- return modelClass.cast(new UsernameEditViewModel());
+ return modelClass.cast(new UsernameEditViewModel(isInRegistration));
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/username/AddAUsernameActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/username/AddAUsernameActivity.kt
new file mode 100644
index 0000000000..6a7a24eaca
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/username/AddAUsernameActivity.kt
@@ -0,0 +1,37 @@
+package org.thoughtcrime.securesms.profiles.username
+
+import android.os.Bundle
+import androidx.navigation.fragment.NavHostFragment
+import org.thoughtcrime.securesms.BaseActivity
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.profiles.manage.UsernameEditFragmentArgs
+import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
+import org.thoughtcrime.securesms.util.DynamicTheme
+
+class AddAUsernameActivity : BaseActivity() {
+ protected open val dynamicTheme: DynamicTheme = DynamicNoActionBarTheme()
+ protected open val contentViewId: Int = R.layout.fragment_container
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(contentViewId)
+ dynamicTheme.onCreate(this)
+
+ if (savedInstanceState == null) {
+ supportFragmentManager.beginTransaction()
+ .replace(
+ R.id.fragment_container,
+ NavHostFragment.create(
+ R.navigation.create_username,
+ UsernameEditFragmentArgs.Builder().setIsInRegistration(true).build().toBundle()
+ )
+ )
+ .commit()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ dynamicTheme.onResume(this)
+ }
+}
diff --git a/app/src/main/res/layout/progress_card.xml b/app/src/main/res/layout/progress_card.xml
new file mode 100644
index 0000000000..0bd315bb58
--- /dev/null
+++ b/app/src/main/res/layout/progress_card.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/username_edit_fragment.xml b/app/src/main/res/layout/username_edit_fragment.xml
index 65d89d3b05..3786ac97cc 100644
--- a/app/src/main/res/layout/username_edit_fragment.xml
+++ b/app/src/main/res/layout/username_edit_fragment.xml
@@ -98,14 +98,45 @@
app:layout_constraintTop_toBottomOf="@id/username_text_wrapper"
app:layout_constraintVertical_bias="0" />
+
+
+
+
@@ -115,7 +146,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
@@ -123,11 +153,23 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
+
+
+ app:constraint_referenced_ids="username_submit_button,username_delete_button,username_skip_button,username_done_button" />
\ No newline at end of file
diff --git a/app/src/main/res/navigation/create_username.xml b/app/src/main/res/navigation/create_username.xml
new file mode 100644
index 0000000000..75a36d7ebb
--- /dev/null
+++ b/app/src/main/res/navigation/create_username.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/edit_profile.xml b/app/src/main/res/navigation/edit_profile.xml
index 548fbca2b4..5cd5e09059 100644
--- a/app/src/main/res/navigation/edit_profile.xml
+++ b/app/src/main/res/navigation/edit_profile.xml
@@ -11,14 +11,6 @@
android:label="fragment_create_profile"
tools:layout="@layout/profile_create_fragment">
-
-
-
-
Send
+
+ Add a username
Choose your username
Username
@@ -1891,6 +1893,10 @@
What is this number?
These digits help keep your username private so you can avoid unwanted messages. Share your username with only the people and groups you\'d like to chat with. If you change usernames you\'ll get a new set of digits.
+
+ Skip
+
+ Done
- %d contact is on Signal!