Java tutorial
/* * Created by Angel Leon (@gubatron), Alden Torres (aldenml), * Marcelina Knitter (@marcelinkaaa), Jose Molina (@votaguz) * Copyright (c) 2011-2018, FrostWire(R). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.frostwire.android.gui.fragments; import android.app.Activity; import android.content.ContentUris; import android.graphics.Point; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.support.v7.view.ActionMode; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.ProgressBar; import android.widget.RelativeLayout; import com.frostwire.android.AndroidPlatform; import com.frostwire.android.R; import com.frostwire.android.core.Constants; import com.frostwire.android.core.FileDescriptor; import com.frostwire.android.gui.Librarian; import com.frostwire.android.gui.adapters.menu.DeleteSingleFileMenuAction; import com.frostwire.android.gui.adapters.menu.FileInformationAction; import com.frostwire.android.gui.adapters.menu.RenameFileMenuAction; import com.frostwire.android.gui.adapters.menu.SeedAction; import com.frostwire.android.gui.adapters.menu.SendFileMenuAction; import com.frostwire.android.gui.adapters.menu.SetAsWallpaperMenuAction; import com.frostwire.android.gui.util.UIUtils; import com.frostwire.android.gui.views.AbstractActivity; import com.frostwire.android.gui.views.AbstractFragment; import com.frostwire.android.gui.views.TouchImageView; import com.frostwire.android.util.ImageLoader; import com.frostwire.android.util.ImageLoader.Callback; import com.frostwire.android.util.SystemUtils; import com.frostwire.util.Logger; import com.frostwire.util.Ref; import org.apache.commons.io.FilenameUtils; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import static com.frostwire.android.util.Asyncs.async; /** * @author gubatron * @author aldenml * @author marcelinkaaa * @author votaguz */ public final class ImageViewerFragment extends AbstractFragment { private static final Logger LOG = Logger.getLogger(ImageViewerFragment.class); public static final String EXTRA_FILE_DESCRIPTOR_BUNDLE = "com.frostwire.android.extra.bundle.FILE_DESCRIPTOR"; public static final String EXTRA_IN_FULL_SCREEN_MODE = "com.frostwire.android.extra.boolean.IN_FULL_SCREEN_MODE"; public static final String EXTRA_ADAPTER_FILE_OFFSET = "com.frostwire.android.extra.int.ADAPTER_FILE_OFFSET"; private int position; boolean inFullScreenMode = false; private boolean highResLoaded = false; private Bundle previousStateBundle; private Bundle nextStateBundle; private FileDescriptor fd; private Uri fileUri; private TouchImageView imageViewHighRes; private ImageViewerActionModeCallback actionModeCallback; private RelativeLayout containerLayout; private ProgressBar progressBar; private ImageLoader imageLoader; public ImageViewerFragment() { super(R.layout.fragment_image_viewer); setHasOptionsMenu(true); this.fd = null; this.actionModeCallback = null; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBundle(EXTRA_FILE_DESCRIPTOR_BUNDLE, fd.toBundle()); outState.putBoolean(EXTRA_IN_FULL_SCREEN_MODE, inFullScreenMode); outState.putInt(EXTRA_ADAPTER_FILE_OFFSET, position); } private void setPreviousStateBundle(Bundle previousImageBundle) { previousStateBundle = previousImageBundle; } private void setNextStateBundle(Bundle nextImageBundle) { nextStateBundle = nextImageBundle; } @Override protected void initComponents(View v, Bundle savedInstanceState) { imageLoader = ImageLoader.getInstance(getActivity()); containerLayout = findView(v, R.id.fragment_image_viewer_container_layout); progressBar = findView(v, R.id.fragment_image_viewer_progress_bar); progressBar.setVisibility(View.VISIBLE); imageViewHighRes = findView(v, R.id.fragment_image_viewer_image_highres); imageViewHighRes.setOnClickListener(new ToggleFullscreenListener(this)); if (savedInstanceState != null) { loadPicture(savedInstanceState); } imageViewHighRes.setOnFlingListener(new PictureFlinger(this)); } @Override public void onResume() { super.onResume(); if (inFullScreenMode) { inFullScreenMode = false; toggleFullScreen(); } } private void toggleFullScreen() { int newFlag = inFullScreenMode ? WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN : WindowManager.LayoutParams.FLAG_FULLSCREEN; int clearFlag = inFullScreenMode ? WindowManager.LayoutParams.FLAG_FULLSCREEN : WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN; getActivity().getWindow().clearFlags(clearFlag); getActivity().getWindow().addFlags(newFlag); int backgroundColor = getActivity().getResources() .getColor(!inFullScreenMode ? R.color.black : R.color.app_background_body_light); containerLayout.setBackgroundColor(backgroundColor); if (!inFullScreenMode) { actionModeCallback.setInFullScreenMode(true); // so it doesn't finish the activity when destroyed actionModeCallback.getActionMode().finish(); } else { actionModeCallback.setInFullScreenMode(false); startActionMode(actionModeCallback); } Toolbar actionBar = ((AbstractActivity) getActivity()).findToolbar(); if (actionBar != null) { actionBar.setVisibility(!inFullScreenMode ? View.GONE : View.VISIBLE); } inFullScreenMode = !inFullScreenMode; LOG.info("toggleFullScreen() my position is " + position); } public void updateData(final FileDescriptor fd, int position) { this.fd = fd; this.position = position; async(this, ImageViewerFragment::loadSurroundingFileDescriptors); if (actionModeCallback == null) { actionModeCallback = new ImageViewerActionModeCallback(this.fd, position); startActionMode(actionModeCallback); } actionModeCallback.getActionMode().setTitle(FilenameUtils.getName(fd.filePath)); fileUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, fd.id); progressBar.setVisibility(View.VISIBLE); highResLoaded = false; // under the assumption that the main image view to render the // picture will cover the main screen, or even go to full screen, // then using the display size as a target is a good idea Point size = displaySize(); // load high res version ImageLoader.Params params = new ImageLoader.Params(); params.targetWidth = size.x; params.targetHeight = size.y; params.placeholderResId = R.drawable.picture_placeholder; params.centerInside = true; params.noCache = true; params.callback = new LoadImageCallback(this); imageLoader.load(fileUri, imageViewHighRes, params); } private final static class PictureFlinger implements TouchImageView.OnFlingListener { private final WeakReference<ImageViewerFragment> fragmentRef; PictureFlinger(ImageViewerFragment fragment) { fragmentRef = Ref.weak(fragment); } @Override public void onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (!Ref.alive(fragmentRef)) { return; } ImageViewerFragment fragment = fragmentRef.get(); float absVelocityX = Math.abs(velocityX); if (fragment.imageViewHighRes.isZoomed() || absVelocityX < 3000) { return; } if (absVelocityX < 0) { // finger moves to the left, load next fragment.loadPicture(fragment.nextStateBundle); } else if (absVelocityX > 0) { // finger moves to the right, load previous fragment.loadPicture(fragment.previousStateBundle); } } } private void loadPicture(Bundle stateBundle) { if (stateBundle != null) { Bundle data = stateBundle.getBundle(EXTRA_FILE_DESCRIPTOR_BUNDLE); int position = stateBundle.getInt(ImageViewerFragment.EXTRA_ADAPTER_FILE_OFFSET, -1); if (data != null) { inFullScreenMode = stateBundle.getBoolean(EXTRA_IN_FULL_SCREEN_MODE); updateData(new FileDescriptor(data), position); } } } private static void loadSurroundingFileDescriptors(ImageViewerFragment fragment) { Activity activity = fragment.getActivity(); int offset = fragment.position; Librarian librarian = Librarian.instance(); List<FileDescriptor> fileDescriptors = new ArrayList<>(0); // We're at the beginning if (offset == 0) { fileDescriptors.addAll(librarian.getFiles(activity, Constants.FILE_TYPE_PICTURES, offset + 1, 1)); fragment.setPreviousStateBundle(null); if (fileDescriptors.size() == 1) { fragment.setNextStateBundle( fragment.prepareFileBundle(fileDescriptors.get(0), offset + 1, fragment.inFullScreenMode)); } } else if (offset > 0) { fileDescriptors.addAll(librarian.getFiles(activity, Constants.FILE_TYPE_PICTURES, offset - 1, 3)); // We're at the end if (fileDescriptors.size() == 2) { fragment.setPreviousStateBundle( fragment.prepareFileBundle(fileDescriptors.get(0), offset - 1, fragment.inFullScreenMode)); fragment.setNextStateBundle(null); } // We're somewhere in the list of files else if (fileDescriptors.size() == 3) { fragment.setPreviousStateBundle( fragment.prepareFileBundle(fileDescriptors.get(0), offset - 1, fragment.inFullScreenMode)); fragment.setPreviousStateBundle( fragment.prepareFileBundle(fileDescriptors.get(2), offset + 1, fragment.inFullScreenMode)); } } } private Bundle prepareFileBundle(FileDescriptor fd, int offset, boolean inFullScreenMode) { Bundle result = new Bundle(); result.putBundle(EXTRA_FILE_DESCRIPTOR_BUNDLE, fd.toBundle()); result.putBoolean(EXTRA_IN_FULL_SCREEN_MODE, inFullScreenMode); result.putInt(EXTRA_ADAPTER_FILE_OFFSET, offset); return result; } private final static class ToggleFullscreenListener implements View.OnClickListener { final WeakReference<ImageViewerFragment> fragmentRef; ToggleFullscreenListener(ImageViewerFragment fragment) { fragmentRef = Ref.weak(fragment); } @Override public void onClick(View view) { if (!Ref.alive(fragmentRef)) { return; } ImageViewerFragment fragment = fragmentRef.get(); if (!fragment.highResLoaded && !fragment.inFullScreenMode) { LOG.info("Not going into full screen, images not loaded"); return; } fragment.toggleFullScreen(); } } private final class ImageViewerActionModeCallback implements android.support.v7.view.ActionMode.Callback { private final FileDescriptor fd; private final int position; private ActionMode mode; private Menu menu; private boolean inFullScreenMode; ImageViewerActionModeCallback(FileDescriptor fd, int position) { this.fd = fd; this.position = position; } ActionMode getActionMode() { return this.mode; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { this.mode = mode; this.menu = menu; mode.getMenuInflater().inflate(R.menu.fragment_my_files_action_mode_menu, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { mode.setTitle(FilenameUtils.getName(fd.filePath)); updateMenuActionsVisibility(fd); return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { Activity context = getActivity(); switch (item.getItemId()) { case R.id.fragment_my_files_action_mode_menu_delete: new DeleteSingleFileMenuAction(context, Constants.FILE_TYPE_PICTURES, fd, (tag, which) -> { if (which == 1) { getActivity().finish(); } }).onClick(); break; case R.id.fragment_my_files_action_mode_menu_seed: new SeedAction(context, fd, null).onClick(); break; case R.id.fragment_my_files_action_mode_menu_open: boolean useFileProvider = SystemUtils.hasNougatOrNewer(); UIUtils.openFile(context, fd.filePath, fd.mime, useFileProvider); break; case R.id.fragment_my_files_action_mode_menu_file_information: new FileInformationAction(context, fd).onClick(); break; case R.id.fragment_my_files_action_mode_menu_use_as_wallpaper: new SetAsWallpaperMenuAction(context, fd).onClick(); mode.finish(); break; case R.id.fragment_my_files_action_mode_menu_rename: final ActionMode fMode = mode; new RenameFileMenuAction(context, null, fd, (tag, which) -> { if (which == 1 && tag != null) { onRenameFileMenuDialogOk(tag, fMode); } }).onClick(); break; case R.id.fragment_my_files_action_mode_menu_share: new SendFileMenuAction(context, fd).onClick(); break; } return true; } @Override public void onDestroyActionMode(ActionMode mode) { this.mode.finish(); if (!inFullScreenMode) { getActivity().finish(); } } private void updateMenuActionsVisibility(FileDescriptor fd) { List<Integer> actionsToHide = new ArrayList<>(); actionsToHide.add(R.id.fragment_my_files_action_mode_menu_use_as_ringtone); actionsToHide.add(R.id.fragment_my_files_action_mode_menu_copy_magnet); actionsToHide.add(R.id.fragment_my_files_action_mode_menu_copy_info_hash); actionsToHide.add(R.id.fragment_my_files_action_mode_menu_add_to_playlist); if (fd.filePath != null && AndroidPlatform.saf(new File(fd.filePath))) { actionsToHide.add(R.id.fragment_my_files_action_mode_menu_seed); } if (menu != null && menu.size() > 0) { for (int i = 0; i < menu.size(); i++) { MenuItem item = menu.getItem(i); item.setVisible(!actionsToHide.contains(item.getItemId())); } } } private void onRenameFileMenuDialogOk(String tag, ActionMode mode) { String newFileName = tag.trim(); String oldFileName = FilenameUtils.getName(fd.filePath); String fileExtension = FilenameUtils.getExtension(fd.filePath); fd.filePath = fd.filePath.replace(oldFileName, newFileName) + "." + fileExtension; mode.setTitle(FilenameUtils.getName(fd.filePath)); updateData(fd, position); } public void setInFullScreenMode(boolean inFullScreenMode) { this.inFullScreenMode = inFullScreenMode; } } private Point displaySize() { Point size = new Point(); getActivity().getWindowManager().getDefaultDisplay().getSize(size); return size; } private static final class LoadImageCallback implements Callback { private final WeakReference<ImageViewerFragment> ref; LoadImageCallback(ImageViewerFragment f) { this.ref = Ref.weak(f); } @Override public void onSuccess() { if (!Ref.alive(ref)) { return; } ImageViewerFragment f = ref.get(); LOG.info("Loaded imageViewHighRes from " + f.fileUri); f.highResLoaded = true; f.progressBar.setVisibility(View.GONE); f.imageViewHighRes.postInvalidate(); } @Override public void onError(Exception e) { if (!Ref.alive(ref)) { return; } ImageViewerFragment f = ref.get(); LOG.info("Could not load imageViewHighRes from " + f.fileUri); f.highResLoaded = false; f.progressBar.setVisibility(View.GONE); f.imageViewHighRes.setImageDrawable(null); UIUtils.showShortMessage(f.getActivity(), "Could not load image"); f.getActivity().finish(); } } }