Made setting profile name and About synchronous operations.

fork-5.53.8
Greyson Parrelli 2021-01-22 13:32:49 -05:00
rodzic 1dc737b5e5
commit ee548d27e5
8 zmienionych plików z 303 dodań i 56 usunięć

Wyświetl plik

@ -1,24 +1,14 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.signal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.util.concurrent.TimeUnit;
@ -30,9 +20,6 @@ public final class ProfileUploadJob extends BaseJob {
public static final String QUEUE = "ProfileAlteration";
private final Context context;
private final SignalServiceAccountManager accountManager;
public ProfileUploadJob() {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
@ -45,9 +32,6 @@ public final class ProfileUploadJob extends BaseJob {
private ProfileUploadJob(@NonNull Parameters parameters) {
super(parameters);
this.context = ApplicationDependencies.getApplication();
this.accountManager = ApplicationDependencies.getSignalServiceAccountManager();
}
@Override
@ -57,17 +41,7 @@ public final class ProfileUploadJob extends BaseJob {
return;
}
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
ProfileName profileName = Recipient.self().getProfileName();
String about = Optional.fromNullable(Recipient.self().getAbout()).or("");
String aboutEmoji = Optional.fromNullable(Recipient.self().getAboutEmoji()).or("");
String avatarPath;
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), about, aboutEmoji, avatar).orNull();
}
DatabaseFactory.getRecipientDatabase(context).setProfileAvatar(Recipient.self().getId(), avatarPath);
ProfileUtil.uploadProfile(context);
}
@Override

Wyświetl plik

@ -9,30 +9,30 @@ import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.dd.CircularProgressButton;
import org.signal.core.util.BreakIteratorCompat;
import org.signal.core.util.EditTextUtil;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.adapter.AlwaysChangedDiffUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
@ -60,9 +60,11 @@ public class EditAboutFragment extends Fragment implements ManageProfileActivity
new AboutPreset("\uD83D\uDE80", R.string.EditAboutFragment_working_on_something_new)
);
private ImageView emojiView;
private EditText bodyView;
private TextView countView;
private ImageView emojiView;
private EditText bodyView;
private TextView countView;
private CircularProgressButton saveButton;
private EditAboutViewModel viewModel;
private String selectedEmoji;
@ -76,6 +78,9 @@ public class EditAboutFragment extends Fragment implements ManageProfileActivity
this.emojiView = view.findViewById(R.id.edit_about_emoji);
this.bodyView = view.findViewById(R.id.edit_about_body);
this.countView = view.findViewById(R.id.edit_about_count);
this.saveButton = view.findViewById(R.id.edit_about_save);
initializeViewModel();
view.<Toolbar>findViewById(R.id.toolbar)
.setNavigationOnClickListener(v -> Navigation.findNavController(view)
@ -92,9 +97,13 @@ public class EditAboutFragment extends Fragment implements ManageProfileActivity
.show(requireFragmentManager(), "BOTTOM");
});
view.findViewById(R.id.edit_about_save).setOnClickListener(this::onSaveClicked);
view.findViewById(R.id.edit_about_clear).setOnClickListener(v -> onClearClicked());
saveButton.setOnClickListener(v -> viewModel.onSaveClicked(requireContext(),
bodyView.getText().toString(),
selectedEmoji));
RecyclerView presetList = view.findViewById(R.id.edit_about_presets);
PresetAdapter presetAdapter = new PresetAdapter();
@ -135,6 +144,13 @@ public class EditAboutFragment extends Fragment implements ManageProfileActivity
}
}
private void initializeViewModel() {
this.viewModel = ViewModelProviders.of(this).get(EditAboutViewModel.class);
viewModel.getSaveState().observe(getViewLifecycleOwner(), this::presentSaveState);
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent);
}
private void presentCount(@NonNull String aboutBody) {
BreakIteratorCompat breakIterator = BreakIteratorCompat.getInstance();
breakIterator.setText(aboutBody);
@ -148,14 +164,26 @@ public class EditAboutFragment extends Fragment implements ManageProfileActivity
}
}
private void onSaveClicked(View view) {
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
DatabaseFactory.getRecipientDatabase(requireContext()).setAbout(Recipient.self().getId(), bodyView.getText().toString(), selectedEmoji);
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
return null;
}, (nothing) -> {
Navigation.findNavController(view).popBackStack();
});
private void presentSaveState(@NonNull EditAboutViewModel.SaveState state) {
switch (state) {
case IDLE:
saveButton.setIndeterminateProgressMode(false);
saveButton.setProgress(0);
break;
case IN_PROGRESS:
saveButton.setIndeterminateProgressMode(true);
saveButton.setProgress(50);
break;
case DONE:
Navigation.findNavController(requireView()).popBackStack();
break;
}
}
private void presentEvent(@NonNull EditAboutViewModel.Event event) {
if (event == EditAboutViewModel.Event.NETWORK_FAILURE) {
Toast.makeText(requireContext(), R.string.EditProfileNameFragment_failed_to_save_due_to_network_issues_try_again_later, Toast.LENGTH_SHORT).show();
}
}
private void onClearClicked() {

Wyświetl plik

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.profiles.manage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
public final class EditAboutViewModel extends ViewModel {
private final ManageProfileRepository repository;
private final MutableLiveData<SaveState> saveState;
private final SingleLiveEvent<Event> events;
public EditAboutViewModel() {
this.repository = new ManageProfileRepository();
this.saveState = new MutableLiveData<>(SaveState.IDLE);
this.events = new SingleLiveEvent<>();
}
@NonNull LiveData<SaveState> getSaveState() {
return saveState;
}
@NonNull LiveData<Event> getEvents() {
return events;
}
void onSaveClicked(@NonNull Context context, @NonNull String about, @NonNull String emoji) {
saveState.setValue(SaveState.IN_PROGRESS);
repository.setAbout(context, about, emoji, result -> {
switch (result) {
case SUCCESS:
saveState.postValue(SaveState.DONE);
break;
case FAILURE_NETWORK:
saveState.postValue(SaveState.IDLE);
events.postValue(Event.NETWORK_FAILURE);
break;
}
});
}
enum SaveState {
IDLE, IN_PROGRESS, DONE
}
enum Event {
NETWORK_FAILURE
}
}

Wyświetl plik

@ -6,13 +6,17 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
import com.dd.CircularProgressButton;
import org.signal.core.util.EditTextUtil;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -31,8 +35,10 @@ public class EditProfileNameFragment extends Fragment {
public static final int NAME_MAX_GLYPHS = 26;
private EditText givenName;
private EditText familyName;
private EditText givenName;
private EditText familyName;
private CircularProgressButton saveButton;
private EditProfileNameViewModel viewModel;
@Override
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@ -43,6 +49,9 @@ public class EditProfileNameFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
this.givenName = view.findViewById(R.id.edit_profile_name_given_name);
this.familyName = view.findViewById(R.id.edit_profile_name_family_name);
this.saveButton = view.findViewById(R.id.edit_profile_name_save);
initializeViewModel();
this.givenName.setText(Recipient.self().getProfileName().getGivenName());
this.familyName.setText(Recipient.self().getProfileName().getFamilyName());
@ -57,19 +66,38 @@ public class EditProfileNameFragment extends Fragment {
this.givenName.addTextChangedListener(new AfterTextChanged(EditProfileNameFragment::trimFieldToMaxByteLength));
this.familyName.addTextChangedListener(new AfterTextChanged(EditProfileNameFragment::trimFieldToMaxByteLength));
view.findViewById(R.id.edit_profile_name_save).setOnClickListener(this::onSaveClicked);
saveButton.setOnClickListener(v -> viewModel.onSaveClicked(requireContext(),
givenName.getText().toString(),
familyName.getText().toString()));
}
private void onSaveClicked(View view) {
ProfileName profileName = ProfileName.fromParts(givenName.getText().toString(), familyName.getText().toString());
private void initializeViewModel() {
this.viewModel = ViewModelProviders.of(this).get(EditProfileNameViewModel.class);
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
DatabaseFactory.getRecipientDatabase(requireContext()).setProfileName(Recipient.self().getId(), profileName);
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
return null;
}, (nothing) -> {
Navigation.findNavController(view).popBackStack();
});
viewModel.getSaveState().observe(getViewLifecycleOwner(), this::presentSaveState);
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent);
}
private void presentSaveState(@NonNull EditProfileNameViewModel.SaveState state) {
switch (state) {
case IDLE:
saveButton.setIndeterminateProgressMode(false);
saveButton.setProgress(0);
break;
case IN_PROGRESS:
saveButton.setIndeterminateProgressMode(true);
saveButton.setProgress(50);
break;
case DONE:
Navigation.findNavController(requireView()).popBackStack();
break;
}
}
private void presentEvent(@NonNull EditProfileNameViewModel.Event event) {
if (event == EditProfileNameViewModel.Event.NETWORK_FAILURE) {
Toast.makeText(requireContext(), R.string.EditProfileNameFragment_failed_to_save_due_to_network_issues_try_again_later, Toast.LENGTH_SHORT).show();
}
}
public static void trimFieldToMaxByteLength(Editable s) {

Wyświetl plik

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.profiles.manage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
public final class EditProfileNameViewModel extends ViewModel {
private final ManageProfileRepository repository;
private final MutableLiveData<SaveState> saveState;
private final SingleLiveEvent<Event> events;
public EditProfileNameViewModel() {
this.repository = new ManageProfileRepository();
this.saveState = new MutableLiveData<>(SaveState.IDLE);
this.events = new SingleLiveEvent<>();
}
@NonNull LiveData<SaveState> getSaveState() {
return saveState;
}
@NonNull LiveData<Event> getEvents() {
return events;
}
void onSaveClicked(@NonNull Context context, @NonNull String givenName, @NonNull String familyName) {
saveState.setValue(SaveState.IN_PROGRESS);
repository.setName(context, ProfileName.fromParts(givenName, familyName), result -> {
switch (result) {
case SUCCESS:
saveState.postValue(SaveState.DONE);
break;
case FAILURE_NETWORK:
saveState.postValue(SaveState.IDLE);
events.postValue(Event.NETWORK_FAILURE);
break;
}
});
}
enum SaveState {
IDLE, IN_PROGRESS, DONE
}
enum Event {
NETWORK_FAILURE
}
}

Wyświetl plik

@ -0,0 +1,50 @@
package org.thoughtcrime.securesms.profiles.manage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ProfileUtil;
import java.io.IOException;
final class ManageProfileRepository {
private static final String TAG = Log.tag(ManageProfileRepository.class);
public void setName(@NonNull Context context, @NonNull ProfileName profileName, @NonNull Consumer<Result> callback) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
ProfileUtil.uploadProfileWithName(context, profileName);
DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), profileName);
callback.accept(Result.SUCCESS);
} catch (IOException e) {
Log.w(TAG, "Failed to upload profile during name change.", e);
callback.accept(Result.FAILURE_NETWORK);
}
});
}
public void setAbout(@NonNull Context context, @NonNull String about, @NonNull String emoji, @NonNull Consumer<Result> callback) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
ProfileUtil.uploadProfileWithAbout(context, about, emoji);
DatabaseFactory.getRecipientDatabase(context).setAbout(Recipient.self().getId(), about, emoji);
callback.accept(Result.SUCCESS);
} catch (IOException e) {
Log.w(TAG, "Failed to upload profile during name change.", e);
callback.accept(Result.FAILURE_NETWORK);
}
});
}
enum Result {
SUCCESS, FAILURE_NETWORK
}
}

Wyświetl plik

@ -9,12 +9,16 @@ import androidx.annotation.WorkerThread;
import org.signal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
@ -26,6 +30,7 @@ import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.internal.util.concurrent.CascadingFuture;
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
@ -96,6 +101,57 @@ public final class ProfileUtil {
return new String(profileCipher.decryptName(Base64.decode(encryptedName)));
}
/**
* Uploads the profile based on all state that's written to disk, except we'll use the provided
* profile name instead. This is useful when you want to ensure that the profile has been uploaded
* successfully before persisting the change to disk.
*/
public static void uploadProfileWithName(@NonNull Context context, @NonNull ProfileName profileName) throws IOException {
uploadProfile(context,
profileName,
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""));
}
/**
* Uploads the profile based on all state that's written to disk, except we'll use the provided
* about/emoji instead. This is useful when you want to ensure that the profile has been uploaded
* successfully before persisting the change to disk.
*/
public static void uploadProfileWithAbout(@NonNull Context context, @NonNull String about, @NonNull String emoji) throws IOException {
uploadProfile(context,
Recipient.self().getProfileName(),
about,
emoji);
}
/**
* Uploads the profile based on all state that's already written to disk.
*/
public static void uploadProfile(@NonNull Context context) throws IOException {
uploadProfile(context,
Recipient.self().getProfileName(),
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""));
}
public static void uploadProfile(@NonNull Context context,
@NonNull ProfileName profileName,
@Nullable String about,
@Nullable String aboutEmoji)
throws IOException
{
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
String avatarPath;
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), about, aboutEmoji, avatar).orNull();
}
DatabaseFactory.getRecipientDatabase(context).setProfileAvatar(Recipient.self().getId(), avatarPath);
}
private static @NonNull ListenableFuture<ProfileAndCredential> getPipeRetrievalFuture(@NonNull SignalServiceAddress address,
@NonNull Optional<ProfileKey> profileKey,
@NonNull Optional<UnidentifiedAccess> unidentifiedAccess,

Wyświetl plik

@ -2021,6 +2021,7 @@
<string name="EditProfileNameFragment_first_name">First name</string>
<string name="EditProfileNameFragment_last_name_optional">Last name (optional)</string>
<string name="EditProfileNameFragment_save">Save</string>
<string name="EditProfileNameFragment_failed_to_save_due_to_network_issues_try_again_later">Failed to save due to network issues. Try again later.</string>
<!-- recipient_preferences_activity -->
<string name="recipient_preference_activity__shared_media">Shared media</string>