Convert and update Manage Storage Settings.

This commit is contained in:
Cody Henthorne
2024-04-30 11:32:27 -04:00
committed by Greyson Parrelli
parent adef572abb
commit 6d657b449c
17 changed files with 764 additions and 703 deletions

View File

@@ -1,47 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MediaTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.preferences.widgets.StorageGraphView;
import java.util.Arrays;
public class ApplicationPreferencesViewModel extends ViewModel {
private final MutableLiveData<StorageGraphView.StorageBreakdown> storageBreakdown = new MutableLiveData<>();
LiveData<StorageGraphView.StorageBreakdown> getStorageBreakdown() {
return storageBreakdown;
}
static ApplicationPreferencesViewModel getApplicationPreferencesViewModel(@NonNull FragmentActivity activity) {
return new ViewModelProvider(activity).get(ApplicationPreferencesViewModel.class);
}
void refreshStorageBreakdown(@NonNull Context context) {
SignalExecutors.BOUNDED.execute(() -> {
MediaTable.StorageBreakdown breakdown = SignalDatabase.media().getStorageBreakdown();
StorageGraphView.StorageBreakdown latestStorageBreakdown = new StorageGraphView.StorageBreakdown(Arrays.asList(
new StorageGraphView.Entry(ContextCompat.getColor(context, R.color.storage_color_photos), breakdown.getPhotoSize()),
new StorageGraphView.Entry(ContextCompat.getColor(context, R.color.storage_color_videos), breakdown.getVideoSize()),
new StorageGraphView.Entry(ContextCompat.getColor(context, R.color.storage_color_files), breakdown.getDocumentSize()),
new StorageGraphView.Entry(ContextCompat.getColor(context, R.color.storage_color_audio), breakdown.getAudioSize())
));
storageBreakdown.postValue(latestStorageBreakdown);
});
}
}

View File

@@ -1,312 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import com.annimon.stream.Stream;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.settings.BaseSettingsAdapter;
import org.thoughtcrime.securesms.components.settings.BaseSettingsFragment;
import org.thoughtcrime.securesms.components.settings.CustomizableSingleSelectSetting;
import org.thoughtcrime.securesms.components.settings.SingleSelectSetting;
import org.thoughtcrime.securesms.components.settings.app.wrapped.SettingsWrapperFragment;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration;
import org.thoughtcrime.securesms.keyvalue.SettingsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.preferences.widgets.StoragePreferenceCategory;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModelList;
import java.text.NumberFormat;
public class StoragePreferenceFragment extends ListSummaryPreferenceFragment {
private Preference keepMessages;
private Preference trimLength;
@Override
public void onCreate(@Nullable Bundle paramBundle) {
super.onCreate(paramBundle);
findPreference("pref_storage_clear_message_history")
.setOnPreferenceClickListener(new ClearMessageHistoryClickListener());
trimLength = findPreference(SettingsValues.THREAD_TRIM_LENGTH);
trimLength.setOnPreferenceClickListener(p -> {
updateToolbarTitle(R.string.preferences__conversation_length_limit);
pushFragment(BaseSettingsFragment.create(new ConversationLengthLimitConfiguration()));
return true;
});
keepMessages = findPreference(SettingsValues.KEEP_MESSAGES_DURATION);
keepMessages.setOnPreferenceClickListener(p -> {
updateToolbarTitle(R.string.preferences__keep_messages);
pushFragment(BaseSettingsFragment.create(new KeepMessagesConfiguration()));
return true;
});
StoragePreferenceCategory storageCategory = (StoragePreferenceCategory) findPreference("pref_storage_category");
FragmentActivity activity = requireActivity();
ApplicationPreferencesViewModel viewModel = ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(activity);
storageCategory.setOnFreeUpSpace(() -> activity.startActivity(MediaOverviewActivity.forAll(activity)));
viewModel.getStorageBreakdown().observe(activity, storageCategory::setStorage);
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
addPreferencesFromResource(R.xml.preferences_storage);
}
@Override
public void onResume() {
super.onResume();
updateToolbarTitle(R.string.preferences__storage);
FragmentActivity activity = requireActivity();
ApplicationPreferencesViewModel viewModel = ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(activity);
viewModel.refreshStorageBreakdown(activity.getApplicationContext());
keepMessages.setSummary(SignalStore.settings().getKeepMessagesDuration().getStringResource());
trimLength.setSummary(SignalStore.settings().isTrimByLengthEnabled() ? getResources().getQuantityString(R.plurals.preferences_storage__s_messages_plural, SignalStore.settings().getThreadTrimLength(), NumberFormat.getInstance().format(SignalStore.settings().getThreadTrimLength()))
: getString(R.string.preferences_storage__none));
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private void updateToolbarTitle(@StringRes int title) {
if (getParentFragment() instanceof SettingsWrapperFragment) {
((SettingsWrapperFragment) getParentFragment()).setTitle(title);
}
}
private void pushFragment(@NonNull Fragment fragment) {
getParentFragmentManager().beginTransaction()
.replace(R.id.wrapped_fragment, fragment)
.addToBackStack(null)
.commit();
}
private class ClearMessageHistoryClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(@NonNull Preference preference) {
new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.preferences_storage__clear_message_history)
.setMessage(R.string.preferences_storage__this_will_delete_all_message_history_and_media_from_your_device)
.setPositiveButton(R.string.delete, (d, w) -> showAreYouReallySure())
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
}
private void showAreYouReallySure() {
new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.preferences_storage__are_you_sure_you_want_to_delete_all_message_history)
.setMessage(R.string.preferences_storage__all_message_history_will_be_permanently_removed_this_action_cannot_be_undone)
.setPositiveButton(R.string.preferences_storage__delete_all_now, (d, w) -> {
SignalExecutors.BOUNDED.execute(() -> {
SignalDatabase.threads().deleteAllConversations();
ApplicationDependencies.getMessageNotifier().updateNotification(requireContext());
});
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
public static class KeepMessagesConfiguration extends BaseSettingsFragment.Configuration implements SingleSelectSetting.SingleSelectSelectionChangedListener {
@Override
public void configureAdapter(@NonNull BaseSettingsAdapter adapter) {
adapter.configureSingleSelect(this);
}
@Override
public @NonNull MappingModelList getSettings() {
KeepMessagesDuration currentDuration = SignalStore.settings().getKeepMessagesDuration();
return Stream.of(KeepMessagesDuration.values())
.map(duration -> new SingleSelectSetting.Item(duration, activity.getString(duration.getStringResource()), null, duration.equals(currentDuration)))
.collect(MappingModelList.toMappingModelList());
}
@Override
public void onSelectionChanged(@NonNull Object selection) {
KeepMessagesDuration currentDuration = SignalStore.settings().getKeepMessagesDuration();
KeepMessagesDuration newDuration = (KeepMessagesDuration) selection;
if (newDuration.ordinal() > currentDuration.ordinal()) {
new MaterialAlertDialogBuilder(activity)
.setTitle(R.string.preferences_storage__delete_older_messages)
.setMessage(activity.getString(R.string.preferences_storage__this_will_permanently_delete_all_message_history_and_media, activity.getString(newDuration.getStringResource())))
.setPositiveButton(R.string.delete, (d, w) -> updateTrimByTime(newDuration))
.setNegativeButton(android.R.string.cancel, null)
.show();
} else {
updateTrimByTime(newDuration);
}
}
private void updateTrimByTime(@NonNull KeepMessagesDuration newDuration) {
SignalStore.settings().setKeepMessagesForDuration(newDuration);
updateSettingsList();
ApplicationDependencies.getTrimThreadsByDateManager().scheduleIfNecessary();
}
}
public static class ConversationLengthLimitConfiguration extends BaseSettingsFragment.Configuration implements CustomizableSingleSelectSetting.CustomizableSingleSelectionListener {
private static final int CUSTOM_LENGTH = -1;
@Override
public void configureAdapter(@NonNull BaseSettingsAdapter adapter) {
adapter.configureSingleSelect(this);
adapter.configureCustomizableSingleSelect(this);
}
@Override
public @NonNull MappingModelList getSettings() {
int trimLength = SignalStore.settings().isTrimByLengthEnabled() ? SignalStore.settings().getThreadTrimLength() : 0;
int[] options = activity.getResources().getIntArray(R.array.conversation_length_limit);
boolean hasSelection = false;
MappingModelList settings = new MappingModelList();
for (int option : options) {
boolean isSelected = option == trimLength;
String text = option == 0 ? activity.getString(R.string.preferences_storage__none)
: activity.getResources().getQuantityString(R.plurals.preferences_storage__s_messages_plural, option, NumberFormat.getInstance().format(option));
settings.add(new SingleSelectSetting.Item(option, text, null, isSelected));
hasSelection = hasSelection || isSelected;
}
int currentValue = SignalStore.settings().getThreadTrimLength();
settings.add(new CustomizableSingleSelectSetting.Item(CUSTOM_LENGTH,
activity.getString(R.string.preferences_storage__custom),
!hasSelection,
currentValue,
activity.getResources().getQuantityString(R.plurals.preferences_storage__s_messages_plural, currentValue, NumberFormat.getInstance().format(currentValue))));
return settings;
}
@SuppressLint("InflateParams")
@Override
public void onCustomizeClicked(@Nullable CustomizableSingleSelectSetting.Item item) {
boolean trimLengthEnabled = SignalStore.settings().isTrimByLengthEnabled();
int trimLength = trimLengthEnabled ? SignalStore.settings().getThreadTrimLength() : 0;
View view = LayoutInflater.from(activity).inflate(R.layout.customizable_setting_edit_text, null, false);
EditText editText = view.findViewById(R.id.customizable_setting_edit_text);
if (trimLength > 0) {
editText.setText(String.valueOf(trimLength));
}
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
.setTitle(R.string.preferences__conversation_length_limit)
.setView(view)
.setPositiveButton(android.R.string.ok, (d, w) -> onSelectionChanged(Integer.parseInt(editText.getText().toString())))
.setNegativeButton(android.R.string.cancel, (d, w) -> updateSettingsList())
.create();
dialog.setOnShowListener(d -> {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!TextUtils.isEmpty(editText.getText()));
editText.requestFocus();
editText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(@NonNull Editable sequence) {
CharSequence trimmed = StringUtil.trimSequence(sequence);
if (TextUtils.isEmpty(trimmed)) {
sequence.replace(0, sequence.length(), "");
} else {
try {
Integer.parseInt(trimmed.toString());
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
return;
} catch (NumberFormatException e) {
String onlyDigits = trimmed.toString().replaceAll("[^\\d]", "");
if (!onlyDigits.equals(trimmed.toString())) {
sequence.replace(0, sequence.length(), onlyDigits);
}
}
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
}
@Override
public void beforeTextChanged(@NonNull CharSequence sequence, int start, int count, int after) {}
@Override
public void onTextChanged(@NonNull CharSequence sequence, int start, int before, int count) {}
});
});
dialog.show();
}
@Override
public void onSelectionChanged(@NonNull Object selection) {
boolean trimLengthEnabled = SignalStore.settings().isTrimByLengthEnabled();
int trimLength = trimLengthEnabled ? SignalStore.settings().getThreadTrimLength() : 0;
int newTrimLength = (Integer) selection;
if (newTrimLength > 0 && (!trimLengthEnabled || newTrimLength < trimLength)) {
new MaterialAlertDialogBuilder(activity)
.setTitle(R.string.preferences_storage__delete_older_messages)
.setMessage(activity.getResources().getQuantityString(R.plurals.preferences_storage__this_will_permanently_trim_all_conversations_to_the_d_most_recent_messages, newTrimLength, newTrimLength))
.setPositiveButton(R.string.delete, (d, w) -> updateTrimByLength(newTrimLength))
.setNegativeButton(android.R.string.cancel, null)
.show();
} else if (newTrimLength == CUSTOM_LENGTH) {
onCustomizeClicked(null);
} else {
updateTrimByLength(newTrimLength);
}
}
private void updateTrimByLength(int length) {
boolean restrictingChange = !SignalStore.settings().isTrimByLengthEnabled() || length < SignalStore.settings().getThreadTrimLength();
SignalStore.settings().setThreadTrimByLengthEnabled(length > 0);
SignalStore.settings().setThreadTrimLength(length);
updateSettingsList();
if (SignalStore.settings().isTrimByLengthEnabled() && restrictingChange) {
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
long trimBeforeDate = keepMessagesDuration != KeepMessagesDuration.FOREVER ? System.currentTimeMillis() - keepMessagesDuration.getDuration()
: ThreadTable.NO_TRIM_BEFORE_DATE_SET;
SignalExecutors.BOUNDED.execute(() -> SignalDatabase.threads().trimAllThreads(length, trimBeforeDate));
}
}
}
}

View File

@@ -1,73 +0,0 @@
package org.thoughtcrime.securesms.preferences.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceViewHolder;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.Util;
public final class StoragePreferenceCategory extends PreferenceCategory {
private Runnable onFreeUpSpace;
private TextView totalSize;
private StorageGraphView storageGraphView;
private StorageGraphView.StorageBreakdown storage;
public StoragePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
public StoragePreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public StoragePreferenceCategory(Context context) {
super(context);
initialize();
}
private void initialize() {
setLayoutResource(R.layout.preference_storage_category);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
totalSize = (TextView) view.findViewById(R.id.total_size);
storageGraphView = (StorageGraphView) view.findViewById(R.id.storageGraphView);
view.findViewById(R.id.free_up_space)
.setOnClickListener(v -> {
if (onFreeUpSpace != null) {
onFreeUpSpace.run();
}
});
totalSize.setText(Util.getPrettyFileSize(0));
if (storage != null) {
setStorage(storage);
}
}
public void setOnFreeUpSpace(Runnable onFreeUpSpace) {
this.onFreeUpSpace = onFreeUpSpace;
}
public void setStorage(StorageGraphView.StorageBreakdown storage) {
this.storage = storage;
if (totalSize != null) {
totalSize.setText(Util.getPrettyFileSize(storage.getTotalSize()));
}
if (storageGraphView != null) {
storageGraphView.setStorageBreakdown(storage);
}
}
}