Add a QR code link and tooltip in the profile settings.

This commit is contained in:
Greyson Parrelli
2023-11-03 10:47:35 -04:00
parent 528ccc1e9d
commit 29350ab7b0
15 changed files with 346 additions and 159 deletions

View File

@@ -14,6 +14,7 @@ public class TooltipValues extends SignalStoreValues {
private static final String GROUP_CALL_TOOLTIP_DISPLAY_COUNT = "tooltip.group_call_tooltip_display_count";
private static final String MULTI_FORWARD_DIALOG = "tooltip.multi.forward.dialog";
private static final String BUBBLE_OPT_OUT = "tooltip.bubble.opt.out";
private static final String PROFILE_SETTINGS_QR_CODE = "tooltip.profile_settings_qr_code";
TooltipValues(@NonNull KeyValueStore store) {
@@ -73,4 +74,12 @@ public class TooltipValues extends SignalStoreValues {
public void markBubbleOptOutTooltipSeen() {
putBoolean(BUBBLE_OPT_OUT, true);
}
public boolean showProfileSettingsQrCodeTooltop() {
return getBoolean(PROFILE_SETTINGS_QR_CODE, true);
}
public void markProfileSettingsQrCodeTooltipSeen() {
putBoolean(PROFILE_SETTINGS_QR_CODE, false);
}
}

View File

@@ -52,7 +52,7 @@ public class CreateProfileActivity extends BaseActivity implements CreateProfile
setContentView(R.layout.create_profile_activity);
if (bundle == null) {
NavHostFragment fragment = NavHostFragment.create(R.navigation.edit_profile, getIntent().getExtras());
NavHostFragment fragment = NavHostFragment.create(R.navigation.create_profile, getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();

View File

@@ -12,12 +12,12 @@ import io.reactivex.rxjava3.subjects.PublishSubject;
public final class EditAboutViewModel extends ViewModel {
private final ManageProfileRepository repository;
private final EditProfileRepository repository;
private final BehaviorSubject<SaveState> saveState;
private final PublishSubject<Event> events;
public EditAboutViewModel() {
this.repository = new ManageProfileRepository();
this.repository = new EditProfileRepository();
this.saveState = BehaviorSubject.createDefault(SaveState.IDLE);
this.events = PublishSubject.create();
}

View File

@@ -14,6 +14,7 @@ import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.map
import androidx.navigation.Navigation.findNavController
import androidx.navigation.fragment.findNavController
import com.airbnb.lottie.SimpleColorFilter
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -32,9 +33,10 @@ import org.thoughtcrime.securesms.databinding.EditProfileFragmentBinding
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarState
import org.thoughtcrime.securesms.profiles.manage.EditProfileViewModel.AvatarState
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameDeleteResult
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.NameUtil.getAbbreviation
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate
@@ -49,7 +51,7 @@ class EditProfileFragment : LoggingFragment() {
private var avatarProgress: AlertDialog? = null
private lateinit var viewModel: ManageProfileViewModel
private lateinit var viewModel: EditProfileViewModel
private lateinit var binding: EditProfileFragmentBinding
private lateinit var disposables: LifecycleDisposable
@@ -128,10 +130,26 @@ class EditProfileFragment : LoggingFragment() {
AvatarPreviewActivity.createTransitionBundle(requireActivity(), binding.manageProfileAvatar)
)
}
if (FeatureFlags.usernames() && SignalStore.account().username != null) {
binding.usernameLinkContainer.setOnClickListener {
findNavController().safeNavigate(EditProfileFragmentDirections.actionManageProfileFragmentToUsernameLinkFragment())
}
if (SignalStore.tooltips().showProfileSettingsQrCodeTooltop()) {
binding.usernameLinkTooltip.visibility = View.VISIBLE
binding.linkTooltipCloseButton.setOnClickListener {
binding.usernameLinkTooltip.visibility = View.GONE
SignalStore.tooltips().markProfileSettingsQrCodeTooltipSeen()
}
}
} else {
binding.usernameLinkContainer.visibility = View.GONE
}
}
private fun initializeViewModel() {
viewModel = ViewModelProvider(this, ManageProfileViewModel.Factory()).get(ManageProfileViewModel::class.java)
viewModel = ViewModelProvider(this, EditProfileViewModel.Factory()).get(EditProfileViewModel::class.java)
LiveDataUtil
.distinctUntilChanged(viewModel.avatar) { b1, b2 -> Arrays.equals(b1.avatar, b2.avatar) }
@@ -185,9 +203,9 @@ class EditProfileFragment : LoggingFragment() {
binding.manageProfileAvatarInitials.visibility = View.GONE
}
if (avatarProgress == null && avatarState.loadingState == ManageProfileViewModel.LoadingState.LOADING) {
if (avatarProgress == null && avatarState.loadingState == EditProfileViewModel.LoadingState.LOADING) {
avatarProgress = SimpleProgressDialog.show(requireContext())
} else if (avatarProgress != null && avatarState.loadingState == ManageProfileViewModel.LoadingState.LOADED) {
} else if (avatarProgress != null && avatarState.loadingState == EditProfileViewModel.LoadingState.LOADED) {
avatarProgress!!.dismiss()
}
}
@@ -251,10 +269,10 @@ class EditProfileFragment : LoggingFragment() {
}
}
private fun presentEvent(event: ManageProfileViewModel.Event) {
private fun presentEvent(event: EditProfileViewModel.Event) {
when (event) {
ManageProfileViewModel.Event.AVATAR_DISK_FAILURE -> Toast.makeText(requireContext(), R.string.ManageProfileFragment_failed_to_set_avatar, Toast.LENGTH_LONG).show()
ManageProfileViewModel.Event.AVATAR_NETWORK_FAILURE -> Toast.makeText(requireContext(), R.string.EditProfileNameFragment_failed_to_save_due_to_network_issues_try_again_later, Toast.LENGTH_LONG).show()
EditProfileViewModel.Event.AVATAR_DISK_FAILURE -> Toast.makeText(requireContext(), R.string.ManageProfileFragment_failed_to_set_avatar, Toast.LENGTH_LONG).show()
EditProfileViewModel.Event.AVATAR_NETWORK_FAILURE -> Toast.makeText(requireContext(), R.string.EditProfileNameFragment_failed_to_save_due_to_network_issues_try_again_later, Toast.LENGTH_LONG).show()
}
}
@@ -284,7 +302,10 @@ class EditProfileFragment : LoggingFragment() {
private fun handleUsernameDeletionResult(usernameDeleteResult: UsernameDeleteResult) {
when (usernameDeleteResult) {
UsernameDeleteResult.SUCCESS -> Snackbar.make(requireView(), R.string.ManageProfileFragment__username_deleted, Snackbar.LENGTH_SHORT).show()
UsernameDeleteResult.SUCCESS -> {
Snackbar.make(requireView(), R.string.ManageProfileFragment__username_deleted, Snackbar.LENGTH_SHORT).show()
binding.usernameLinkContainer.visibility = View.GONE
}
UsernameDeleteResult.NETWORK_ERROR -> Snackbar.make(requireView(), R.string.ManageProfileFragment__couldnt_delete_username, Snackbar.LENGTH_SHORT).show()
}
}

View File

@@ -14,12 +14,12 @@ import org.signal.core.util.StringUtil;
public final class EditProfileNameViewModel extends ViewModel {
private final ManageProfileRepository repository;
private final EditProfileRepository repository;
private final MutableLiveData<SaveState> saveState;
private final SingleLiveEvent<Event> events;
public EditProfileNameViewModel() {
this.repository = new ManageProfileRepository();
this.repository = new EditProfileRepository();
this.saveState = new MutableLiveData<>(SaveState.IDLE);
this.events = new SingleLiveEvent<>();
}

View File

@@ -20,9 +20,9 @@ import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.ByteArrayInputStream;
import java.io.IOException;
final class ManageProfileRepository {
final class EditProfileRepository {
private static final String TAG = Log.tag(ManageProfileRepository.class);
private static final String TAG = Log.tag(EditProfileRepository.class);
public void setName(@NonNull Context context, @NonNull ProfileName profileName, @NonNull Consumer<Result> callback) {
SignalExecutors.UNBOUNDED.execute(() -> {

View File

@@ -37,9 +37,9 @@ import java.util.Optional;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
class ManageProfileViewModel extends ViewModel {
class EditProfileViewModel extends ViewModel {
private static final String TAG = Log.tag(ManageProfileViewModel.class);
private static final String TAG = Log.tag(EditProfileViewModel.class);
private final MutableLiveData<InternalAvatarState> internalAvatarState;
private final MutableLiveData<ProfileName> profileName;
@@ -49,20 +49,20 @@ class ManageProfileViewModel extends ViewModel {
private final LiveData<AvatarState> avatarState;
private final SingleLiveEvent<Event> events;
private final RecipientForeverObserver observer;
private final ManageProfileRepository repository;
private final EditProfileRepository repository;
private final UsernameRepository usernameEditRepository;
private final MutableLiveData<Optional<Badge>> badge;
private byte[] previousAvatar;
public ManageProfileViewModel() {
public EditProfileViewModel() {
this.internalAvatarState = new MutableLiveData<>();
this.profileName = new MutableLiveData<>();
this.username = new MutableLiveData<>();
this.about = new MutableLiveData<>();
this.aboutEmoji = new MutableLiveData<>();
this.events = new SingleLiveEvent<>();
this.repository = new ManageProfileRepository();
this.repository = new EditProfileRepository();
this.usernameEditRepository = new UsernameRepository();
this.badge = new DefaultValueLiveData<>(Optional.empty());
this.observer = this::onRecipientChanged;
@@ -281,7 +281,7 @@ class ManageProfileViewModel extends ViewModel {
static class Factory extends ViewModelProvider.NewInstanceFactory {
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return Objects.requireNonNull(modelClass.cast(new ManageProfileViewModel()));
return Objects.requireNonNull(modelClass.cast(new EditProfileViewModel()));
}
}

View File

@@ -232,6 +232,8 @@ class UsernameRepository {
return try {
accountManager.deleteUsername()
SignalDatabase.recipients.setUsername(Recipient.self().id, null)
SignalStore.account().username = null
SignalStore.account().usernameLink = null
SignalStore.account().usernameOutOfSync = false
Log.i(TAG, "[deleteUsername] Successfully deleted the username.")
UsernameDeleteResult.SUCCESS