That fuck shit the fascists are using
at master 419 lines 16 kB view raw
1package org.tm.archive.mediaoverview; 2 3import android.app.ActivityOptions; 4import android.content.ActivityNotFoundException; 5import android.content.Context; 6import android.content.Intent; 7import android.content.res.Configuration; 8import android.net.Uri; 9import android.os.Bundle; 10import android.view.LayoutInflater; 11import android.view.Menu; 12import android.view.MenuItem; 13import android.view.View; 14import android.view.ViewGroup; 15import android.widget.TextView; 16import android.widget.Toast; 17 18import androidx.annotation.NonNull; 19import androidx.annotation.Nullable; 20import androidx.appcompat.app.AppCompatActivity; 21import androidx.appcompat.view.ActionMode; 22import androidx.fragment.app.Fragment; 23import androidx.fragment.app.FragmentActivity; 24import androidx.lifecycle.Observer; 25import androidx.loader.app.LoaderManager; 26import androidx.loader.content.Loader; 27import androidx.recyclerview.widget.RecyclerView; 28 29import com.bumptech.glide.Glide; 30import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager; 31 32import org.signal.core.util.DimensionUnit; 33import org.signal.core.util.logging.Log; 34import org.tm.archive.R; 35import org.tm.archive.attachments.DatabaseAttachment; 36import org.tm.archive.components.menu.ActionItem; 37import org.tm.archive.components.menu.SignalBottomActionBar; 38import org.tm.archive.components.voice.VoiceNoteMediaController; 39import org.tm.archive.components.voice.VoiceNotePlaybackState; 40import org.tm.archive.database.MediaTable; 41import org.tm.archive.database.loaders.GroupedThreadMediaLoader; 42import org.tm.archive.database.loaders.MediaLoader; 43import org.tm.archive.mediapreview.MediaIntentFactory; 44import org.tm.archive.mediapreview.MediaPreviewV2Activity; 45import org.tm.archive.mms.PartAuthority; 46import org.tm.archive.util.BottomOffsetDecoration; 47import org.tm.archive.util.MediaUtil; 48import org.tm.archive.util.Util; 49import org.tm.archive.util.ViewUtil; 50 51import java.util.Arrays; 52import java.util.Objects; 53 54public final class MediaOverviewPageFragment extends Fragment 55 implements MediaGalleryAllAdapter.ItemClickListener, 56 MediaGalleryAllAdapter.AudioItemListener, 57 LoaderManager.LoaderCallbacks<GroupedThreadMediaLoader.GroupedThreadMedia> 58{ 59 60 private static final String TAG = Log.tag(MediaOverviewPageFragment.class); 61 62 private static final String THREAD_ID_EXTRA = "thread_id"; 63 private static final String MEDIA_TYPE_EXTRA = "media_type"; 64 private static final String GRID_MODE = "grid_mode"; 65 66 private final ActionModeCallback actionModeCallback = new ActionModeCallback(); 67 private MediaTable.Sorting sorting = MediaTable.Sorting.Newest; 68 private MediaLoader.MediaType mediaType = MediaLoader.MediaType.GALLERY; 69 private long threadId; 70 private TextView noMedia; 71 private RecyclerView recyclerView; 72 private StickyHeaderGridLayoutManager gridManager; 73 private ActionMode actionMode; 74 private boolean detail; 75 private MediaGalleryAllAdapter adapter; 76 private GridMode gridMode; 77 private VoiceNoteMediaController voiceNoteMediaController; 78 private SignalBottomActionBar bottomActionBar; 79 80 public static @NonNull Fragment newInstance(long threadId, 81 @NonNull MediaLoader.MediaType mediaType, 82 @NonNull GridMode gridMode) 83 { 84 MediaOverviewPageFragment mediaOverviewAllFragment = new MediaOverviewPageFragment(); 85 Bundle args = new Bundle(); 86 args.putLong(THREAD_ID_EXTRA, threadId); 87 args.putInt(MEDIA_TYPE_EXTRA, mediaType.ordinal()); 88 args.putInt(GRID_MODE, gridMode.ordinal()); 89 mediaOverviewAllFragment.setArguments(args); 90 91 return mediaOverviewAllFragment; 92 } 93 94 @Override 95 public void onCreate(Bundle bundle) { 96 super.onCreate(bundle); 97 98 Bundle arguments = requireArguments(); 99 100 threadId = arguments.getLong(THREAD_ID_EXTRA, Long.MIN_VALUE); 101 mediaType = MediaLoader.MediaType.values()[arguments.getInt(MEDIA_TYPE_EXTRA)]; 102 gridMode = GridMode.values()[arguments.getInt(GRID_MODE)]; 103 104 if (threadId == Long.MIN_VALUE) throw new AssertionError(); 105 106 LoaderManager.getInstance(this).initLoader(0, null, this); 107 } 108 109 @Override 110 public void onActivityCreated(@Nullable Bundle savedInstanceState) { 111 super.onActivityCreated(savedInstanceState); 112 113 voiceNoteMediaController = new VoiceNoteMediaController(requireActivity(), false); 114 } 115 116 @Override 117 public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 118 Context context = requireContext(); 119 View view = inflater.inflate(R.layout.media_overview_page_fragment, container, false); 120 int spans = getResources().getInteger(R.integer.media_overview_cols); 121 122 this.recyclerView = view.findViewById(R.id.media_grid); 123 this.noMedia = view.findViewById(R.id.no_images); 124 this.bottomActionBar = view.findViewById(R.id.media_overview_bottom_action_bar); 125 this.gridManager = new StickyHeaderGridLayoutManager(spans); 126 127 this.adapter = new MediaGalleryAllAdapter(context, 128 Glide.with(this), 129 new GroupedThreadMediaLoader.EmptyGroupedThreadMedia(), 130 this, 131 this, 132 sorting.isRelatedToFileSize(), 133 threadId == MediaTable.ALL_THREADS); 134 this.recyclerView.setAdapter(adapter); 135 this.recyclerView.setLayoutManager(gridManager); 136 this.recyclerView.setHasFixedSize(true); 137 this.recyclerView.addItemDecoration(new MediaGridDividerDecoration(spans, ViewUtil.dpToPx(4), adapter)); 138 this.recyclerView.addItemDecoration(new BottomOffsetDecoration(ViewUtil.dpToPx(160))); 139 140 MediaOverviewViewModel viewModel = MediaOverviewViewModel.getMediaOverviewViewModel(requireActivity()); 141 142 viewModel.getSortOrder() 143 .observe(getViewLifecycleOwner(), sorting -> { 144 if (sorting != null) { 145 this.sorting = sorting; 146 adapter.setShowFileSizes(sorting.isRelatedToFileSize()); 147 LoaderManager.getInstance(this).restartLoader(0, null, this); 148 updateMultiSelect(); 149 } 150 }); 151 152 if (gridMode == GridMode.FOLLOW_MODEL) { 153 viewModel.getDetailLayout() 154 .observe(getViewLifecycleOwner(), this::setDetailView); 155 } else { 156 setDetailView(gridMode == GridMode.FIXED_DETAIL); 157 } 158 159 return view; 160 } 161 162 private void setDetailView(boolean detail) { 163 this.detail = detail; 164 adapter.setDetailView(detail); 165 refreshLayoutManager(); 166 updateMultiSelect(); 167 } 168 169 @Override 170 public void onConfigurationChanged(@NonNull Configuration newConfig) { 171 super.onConfigurationChanged(newConfig); 172 if (gridManager != null) { 173 refreshLayoutManager(); 174 } 175 } 176 177 private void refreshLayoutManager() { 178 this.gridManager = new StickyHeaderGridLayoutManager(detail ? 1 : getResources().getInteger(R.integer.media_overview_cols)); 179 this.recyclerView.setLayoutManager(gridManager); 180 } 181 182 @Override 183 public @NonNull Loader<GroupedThreadMediaLoader.GroupedThreadMedia> onCreateLoader(int i, Bundle bundle) { 184 return new GroupedThreadMediaLoader(requireContext(), threadId, mediaType, sorting); 185 } 186 187 @Override 188 public void onLoadFinished(@NonNull Loader<GroupedThreadMediaLoader.GroupedThreadMedia> loader, GroupedThreadMediaLoader.GroupedThreadMedia groupedThreadMedia) { 189 ((MediaGalleryAllAdapter) recyclerView.getAdapter()).setMedia(groupedThreadMedia); 190 ((MediaGalleryAllAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged(); 191 192 noMedia.setVisibility(recyclerView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE); 193 getActivity().invalidateOptionsMenu(); 194 } 195 196 @Override 197 public void onLoaderReset(@NonNull Loader<GroupedThreadMediaLoader.GroupedThreadMedia> cursorLoader) { 198 ((MediaGalleryAllAdapter) recyclerView.getAdapter()).setMedia(new GroupedThreadMediaLoader.EmptyGroupedThreadMedia()); 199 } 200 201 @Override 202 public void onMediaClicked(@NonNull View view, @NonNull MediaTable.MediaRecord mediaRecord) { 203 if (actionMode != null) { 204 handleMediaMultiSelectClick(mediaRecord); 205 } else { 206 handleMediaPreviewClick(view, mediaRecord); 207 } 208 } 209 210 @Override 211 public void onDestroy() { 212 super.onDestroy(); 213 if (recyclerView != null) { 214 int childCount = recyclerView.getChildCount(); 215 for (int i = 0; i < childCount; i++) { 216 adapter.detach(recyclerView.getChildViewHolder(recyclerView.getChildAt(i))); 217 } 218 } 219 } 220 221 private void handleMediaMultiSelectClick(@NonNull MediaTable.MediaRecord mediaRecord) { 222 MediaGalleryAllAdapter adapter = getListAdapter(); 223 224 adapter.toggleSelection(mediaRecord); 225 if (adapter.getSelectedMediaCount() == 0) { 226 actionMode.finish(); 227 } else { 228 updateMultiSelect(); 229 } 230 } 231 232 private void handleMediaPreviewClick(@NonNull View view, @NonNull MediaTable.MediaRecord mediaRecord) { 233 if (mediaRecord.getAttachment().getUri() == null) { 234 return; 235 } 236 237 Context context = getContext(); 238 if (context == null) { 239 return; 240 } 241 242 DatabaseAttachment attachment = mediaRecord.getAttachment(); 243 244 if (MediaUtil.isVideo(attachment) || MediaUtil.isImage(attachment)) { 245 MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs( 246 threadId, 247 mediaRecord.getDate(), 248 Objects.requireNonNull(mediaRecord.getAttachment().getUri()), 249 mediaRecord.getContentType(), 250 mediaRecord.getAttachment().size, 251 mediaRecord.getAttachment().caption, 252 true, 253 true, 254 threadId == MediaTable.ALL_THREADS, 255 true, 256 sorting, 257 attachment.videoGif, 258 new MediaIntentFactory.SharedElementArgs( 259 attachment.width, 260 attachment.height, 261 DimensionUnit.DP.toDp(12), 262 DimensionUnit.DP.toDp(12), 263 DimensionUnit.DP.toDp(12), 264 DimensionUnit.DP.toDp(12) 265 ), 266 false); 267 view.setTransitionName(MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME); 268 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(requireActivity(), view, MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME); 269 context.startActivity(MediaIntentFactory.create(context, args), options.toBundle()); 270 } else { 271 if (!MediaUtil.isAudio(attachment)) { 272 showFileExternally(context, mediaRecord); 273 } 274 } 275 } 276 277 private static void showFileExternally(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord) { 278 Uri uri = mediaRecord.getAttachment().getUri(); 279 280 Intent intent = new Intent(Intent.ACTION_VIEW); 281 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 282 intent.setDataAndType(PartAuthority.getAttachmentPublicUri(uri), mediaRecord.getContentType()); 283 try { 284 context.startActivity(intent); 285 } catch (ActivityNotFoundException e) { 286 Log.w(TAG, "No activity existed to view the media."); 287 Toast.makeText(context, R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show(); 288 } 289 } 290 291 @Override 292 public void onMediaLongClicked(MediaTable.MediaRecord mediaRecord) { 293 if (actionMode == null) { 294 enterMultiSelect(); 295 } 296 297 handleMediaMultiSelectClick(mediaRecord); 298 } 299 300 private void handleSelectAllMedia() { 301 getListAdapter().selectAllMedia(); 302 updateMultiSelect(); 303 } 304 305 private String getActionModeTitle() { 306 MediaGalleryAllAdapter adapter = getListAdapter(); 307 int mediaCount = adapter.getSelectedMediaCount(); 308 long totalFileSize = adapter.getSelectedMediaTotalFileSize(); 309 310 return getResources().getQuantityString(R.plurals.MediaOverviewActivity_d_selected_s, 311 mediaCount, 312 mediaCount, 313 Util.getPrettyFileSize(totalFileSize)); 314 } 315 316 private MediaGalleryAllAdapter getListAdapter() { 317 return (MediaGalleryAllAdapter) recyclerView.getAdapter(); 318 } 319 320 private void enterMultiSelect() { 321 FragmentActivity activity = requireActivity(); 322 actionMode = ((AppCompatActivity) activity).startSupportActionMode(actionModeCallback); 323 ((MediaOverviewActivity) activity).onEnterMultiSelect(); 324 ViewUtil.animateIn(bottomActionBar, bottomActionBar.getEnterAnimation()); 325 updateMultiSelect(); 326 } 327 328 private void exitMultiSelect() { 329 actionMode.finish(); 330 actionMode = null; 331 ViewUtil.animateOut(bottomActionBar, bottomActionBar.getExitAnimation()); 332 } 333 334 private void updateMultiSelect() { 335 if (actionMode != null) { 336 actionMode.setTitle(getActionModeTitle()); 337 338 int selectionCount = getListAdapter().getSectionCount(); 339 340 bottomActionBar.setItems(Arrays.asList( 341 new ActionItem(R.drawable.symbol_save_android_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_save_plural, selectionCount), () -> { 342 MediaActions.handleSaveMedia(MediaOverviewPageFragment.this, 343 getListAdapter().getSelectedMedia(), 344 this::exitMultiSelect); 345 }), 346 new ActionItem(R.drawable.symbol_check_circle_24, getString(R.string.MediaOverviewActivity_select_all), this::handleSelectAllMedia), 347 new ActionItem(R.drawable.symbol_trash_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_delete_plural, selectionCount), () -> { 348 MediaActions.handleDeleteMedia(requireContext(), getListAdapter().getSelectedMedia()); 349 exitMultiSelect(); 350 }) 351 )); 352 } 353 } 354 355 356 @Override 357 public void onPlay(@NonNull Uri audioUri, double progress, long messageId) { 358 voiceNoteMediaController.startSinglePlayback(audioUri, messageId, progress); 359 } 360 361 @Override 362 public void onPause(@NonNull Uri audioUri) { 363 voiceNoteMediaController.pausePlayback(audioUri); 364 } 365 366 @Override 367 public void onSeekTo(@NonNull Uri audioUri, double progress) { 368 voiceNoteMediaController.seekToPosition(audioUri, progress); 369 } 370 371 @Override 372 public void onStopAndReset(@NonNull Uri audioUri) { 373 voiceNoteMediaController.stopPlaybackAndReset(audioUri); 374 } 375 376 @Override 377 public void registerPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer) { 378 voiceNoteMediaController.getVoiceNotePlaybackState().observe(getViewLifecycleOwner(), observer); 379 } 380 381 @Override 382 public void unregisterPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer) { 383 voiceNoteMediaController.getVoiceNotePlaybackState().removeObserver(observer); 384 } 385 386 private class ActionModeCallback implements ActionMode.Callback { 387 388 @Override 389 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 390 mode.setTitle(getActionModeTitle()); 391 return true; 392 } 393 394 @Override 395 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 396 return false; 397 } 398 399 @Override 400 public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) { 401 return false; 402 } 403 404 @Override 405 public void onDestroyActionMode(ActionMode mode) { 406 getListAdapter().clearSelection(); 407 408 FragmentActivity activity = requireActivity(); 409 ((MediaOverviewActivity) activity).onExitMultiSelect(); 410 411 exitMultiSelect(); 412 } 413 } 414 415 public enum GridMode { 416 FIXED_DETAIL, 417 FOLLOW_MODEL 418 } 419}