@ -0,0 +1,32 @@ |
|||||||
|
package com.bilibili.boxing_impl; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes; |
||||||
|
|
||||||
|
/** |
||||||
|
* Help getting the resource in config. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
|
||||||
|
public class BoxingResHelper { |
||||||
|
|
||||||
|
@DrawableRes |
||||||
|
public static int getMediaCheckedRes() { |
||||||
|
int result = BoxingManager.getInstance().getBoxingConfig().getMediaCheckedRes(); |
||||||
|
return result > 0 ? result : R.drawable.ic_boxing_checked; |
||||||
|
} |
||||||
|
|
||||||
|
@DrawableRes |
||||||
|
public static int getMediaUncheckedRes() { |
||||||
|
int result = BoxingManager.getInstance().getBoxingConfig().getMediaUnCheckedRes(); |
||||||
|
return result > 0 ? result : R.drawable.shape_boxing_unchecked; |
||||||
|
} |
||||||
|
|
||||||
|
@DrawableRes |
||||||
|
public static int getCameraRes() { |
||||||
|
return BoxingManager.getInstance().getBoxingConfig().getCameraRes(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,89 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.util.DisplayMetrics; |
||||||
|
import android.util.TypedValue; |
||||||
|
import android.view.Display; |
||||||
|
import android.view.WindowManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class WindowManagerHelper { |
||||||
|
private static WindowManager getWindowManager(Context context) { |
||||||
|
Object service = context.getSystemService(Context.WINDOW_SERVICE); |
||||||
|
if (service == null) |
||||||
|
return null; |
||||||
|
|
||||||
|
return (WindowManager) service; |
||||||
|
} |
||||||
|
|
||||||
|
private static Display getDefaultDisplay(Context context) { |
||||||
|
WindowManager wm = getWindowManager(context); |
||||||
|
if (wm == null) |
||||||
|
return null; |
||||||
|
|
||||||
|
return wm.getDefaultDisplay(); |
||||||
|
} |
||||||
|
|
||||||
|
public static int getScreenHeight(Context context) { |
||||||
|
DisplayMetrics dm = getDisplayMetrics(context); |
||||||
|
if (dm != null) { |
||||||
|
return dm.heightPixels; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
public static int getScreenWidth(Context context) { |
||||||
|
DisplayMetrics dm = getDisplayMetrics(context); |
||||||
|
if (dm != null) { |
||||||
|
return dm.widthPixels; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static DisplayMetrics getDisplayMetrics(Context context) { |
||||||
|
Display display = getDefaultDisplay(context); |
||||||
|
if (display != null) { |
||||||
|
DisplayMetrics result = new DisplayMetrics(); |
||||||
|
display.getMetrics(result); |
||||||
|
return result; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public static int getStatusBarHeight(Context context) { |
||||||
|
int result = 0; |
||||||
|
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); |
||||||
|
if (resourceId > 0) { |
||||||
|
result = context.getResources().getDimensionPixelSize(resourceId); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public static int getToolbarHeight(Context context) { |
||||||
|
TypedValue tv = new TypedValue(); |
||||||
|
if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { |
||||||
|
return TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.adapter; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.text.TextUtils; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ImageView; |
||||||
|
import android.widget.TextView; |
||||||
|
|
||||||
|
import com.bilibili.boxing.BoxingMediaLoader; |
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Album window adapter. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingAlbumAdapter extends RecyclerView.Adapter implements View.OnClickListener { |
||||||
|
private static final String UNKNOW_ALBUM_NAME = "?"; |
||||||
|
|
||||||
|
private int mCurrentAlbumPos; |
||||||
|
|
||||||
|
private List<AlbumEntity> mAlums; |
||||||
|
private LayoutInflater mInflater; |
||||||
|
private OnAlbumClickListener mAlbumOnClickListener; |
||||||
|
private int mDefaultRes; |
||||||
|
|
||||||
|
public BoxingAlbumAdapter(Context context) { |
||||||
|
this.mAlums = new ArrayList<>(); |
||||||
|
this.mAlums.add(AlbumEntity.createDefaultAlbum()); |
||||||
|
this.mInflater = LayoutInflater.from(context); |
||||||
|
this.mDefaultRes = BoxingManager.getInstance().getBoxingConfig().getAlbumPlaceHolderRes(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setAlbumOnClickListener(OnAlbumClickListener albumOnClickListener) { |
||||||
|
this.mAlbumOnClickListener = albumOnClickListener; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
||||||
|
return new AlbumViewHolder(mInflater.inflate(R.layout.layout_boxing_album_item, parent, false)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { |
||||||
|
final AlbumViewHolder albumViewHolder = (AlbumViewHolder) holder; |
||||||
|
albumViewHolder.mCoverImg.setImageResource(mDefaultRes); |
||||||
|
final int adapterPos = holder.getAdapterPosition(); |
||||||
|
final AlbumEntity album = mAlums.get(adapterPos); |
||||||
|
|
||||||
|
if (album != null && album.hasImages()) { |
||||||
|
String albumName = TextUtils.isEmpty(album.mBucketName) ? |
||||||
|
albumViewHolder.mNameTxt.getContext().getString(R.string.boxing_default_album_name) :album.mBucketName; |
||||||
|
albumViewHolder.mNameTxt.setText(albumName); |
||||||
|
ImageMedia media = (ImageMedia) album.mImageList.get(0); |
||||||
|
if (media != null) { |
||||||
|
BoxingMediaLoader.getInstance().displayThumbnail(albumViewHolder.mCoverImg, media.getPath(), 50, 50); |
||||||
|
albumViewHolder.mCoverImg.setTag(R.string.boxing_app_name, media.getPath()); |
||||||
|
} |
||||||
|
albumViewHolder.mLayout.setTag(adapterPos); |
||||||
|
albumViewHolder.mLayout.setOnClickListener(this); |
||||||
|
albumViewHolder.mCheckedImg.setVisibility(album.mIsSelected ? View.VISIBLE : View.GONE); |
||||||
|
albumViewHolder.mSizeTxt.setText(albumViewHolder.mSizeTxt. |
||||||
|
getResources().getString(R.string.boxing_album_images_fmt, album.mCount)); |
||||||
|
} else { |
||||||
|
albumViewHolder.mNameTxt.setText(UNKNOW_ALBUM_NAME); |
||||||
|
albumViewHolder.mSizeTxt.setVisibility(View.GONE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void addAllData(List<AlbumEntity> alums) { |
||||||
|
mAlums.clear(); |
||||||
|
mAlums.addAll(alums); |
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
public List<AlbumEntity> getAlums() { |
||||||
|
return mAlums; |
||||||
|
} |
||||||
|
|
||||||
|
public int getCurrentAlbumPos() { |
||||||
|
return mCurrentAlbumPos; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCurrentAlbumPos(int currentAlbumPos) { |
||||||
|
mCurrentAlbumPos = currentAlbumPos; |
||||||
|
} |
||||||
|
|
||||||
|
public AlbumEntity getCurrentAlbum() { |
||||||
|
if (mAlums == null || mAlums.size() <= 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return mAlums.get(mCurrentAlbumPos); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getItemCount() { |
||||||
|
return mAlums != null ? mAlums.size() : 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
int id = v.getId(); |
||||||
|
if (id == R.id.album_layout) { |
||||||
|
if (mAlbumOnClickListener != null) { |
||||||
|
mAlbumOnClickListener.onClick(v, (Integer) v.getTag()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class AlbumViewHolder extends RecyclerView.ViewHolder { |
||||||
|
ImageView mCoverImg; |
||||||
|
TextView mNameTxt; |
||||||
|
TextView mSizeTxt; |
||||||
|
View mLayout; |
||||||
|
ImageView mCheckedImg; |
||||||
|
|
||||||
|
AlbumViewHolder(final View itemView) { |
||||||
|
super(itemView); |
||||||
|
mCoverImg = (ImageView) itemView.findViewById(R.id.album_thumbnail); |
||||||
|
mNameTxt = (TextView) itemView.findViewById(R.id.album_name); |
||||||
|
mSizeTxt = (TextView) itemView.findViewById(R.id.album_size); |
||||||
|
mLayout = itemView.findViewById(R.id.album_layout); |
||||||
|
mCheckedImg = (ImageView) itemView.findViewById(R.id.album_checked); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public interface OnAlbumClickListener { |
||||||
|
void onClick(View view, int pos); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,212 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.adapter; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ImageView; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing_impl.BoxingResHelper; |
||||||
|
import com.bilibili.boxing_impl.view.MediaItemLayout; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A RecyclerView.Adapter for image or video picker showing. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingMediaAdapter extends RecyclerView.Adapter { |
||||||
|
private static final int CAMERA_TYPE = 0; |
||||||
|
private static final int NORMAL_TYPE = 1; |
||||||
|
|
||||||
|
private int mOffset; |
||||||
|
private boolean mMultiImageMode; |
||||||
|
|
||||||
|
private List<BaseMedia> mMedias; |
||||||
|
private List<BaseMedia> mSelectedMedias; |
||||||
|
private LayoutInflater mInflater; |
||||||
|
private BoxingConfig mMediaConfig; |
||||||
|
private View.OnClickListener mOnCameraClickListener; |
||||||
|
private View.OnClickListener mOnMediaClickListener; |
||||||
|
private OnCheckListener mOnCheckListener; |
||||||
|
private OnMediaCheckedListener mOnCheckedListener; |
||||||
|
private int mDefaultRes; |
||||||
|
|
||||||
|
public BoxingMediaAdapter(Context context) { |
||||||
|
this.mInflater = LayoutInflater.from(context); |
||||||
|
this.mMedias = new ArrayList<>(); |
||||||
|
this.mSelectedMedias = new ArrayList<>(9); |
||||||
|
this.mMediaConfig = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
this.mOffset = mMediaConfig.isNeedCamera() ? 1 : 0; |
||||||
|
this.mMultiImageMode = mMediaConfig.getMode() == BoxingConfig.Mode.MULTI_IMG; |
||||||
|
this.mOnCheckListener = new OnCheckListener(); |
||||||
|
this.mDefaultRes = mMediaConfig.getMediaPlaceHolderRes(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getItemViewType(int position) { |
||||||
|
if (position == 0 && mMediaConfig.isNeedCamera()) { |
||||||
|
return CAMERA_TYPE; |
||||||
|
} |
||||||
|
return NORMAL_TYPE; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
||||||
|
if (CAMERA_TYPE == viewType) { |
||||||
|
return new CameraViewHolder(mInflater.inflate(R.layout.layout_boxing_recycleview_header, parent, false)); |
||||||
|
} |
||||||
|
return new ImageViewHolder(mInflater.inflate(R.layout.layout_boxing_recycleview_item, parent, false)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { |
||||||
|
if (holder instanceof CameraViewHolder) { |
||||||
|
CameraViewHolder viewHolder = (CameraViewHolder) holder; |
||||||
|
viewHolder.mCameraLayout.setOnClickListener(mOnCameraClickListener); |
||||||
|
viewHolder.mCameraImg.setImageResource(BoxingResHelper.getCameraRes()); |
||||||
|
} else { |
||||||
|
int pos = position - mOffset; |
||||||
|
final BaseMedia media = mMedias.get(pos); |
||||||
|
final ImageViewHolder vh = (ImageViewHolder) holder; |
||||||
|
|
||||||
|
vh.mItemLayout.setImageRes(mDefaultRes); |
||||||
|
vh.mItemLayout.setTag(media); |
||||||
|
|
||||||
|
vh.mItemLayout.setOnClickListener(mOnMediaClickListener); |
||||||
|
vh.mItemLayout.setTag(R.id.media_item_check, pos); |
||||||
|
vh.mItemLayout.setMedia(media); |
||||||
|
vh.mItemChecked.setVisibility(mMultiImageMode ? View.VISIBLE : View.GONE); |
||||||
|
if (mMultiImageMode && media instanceof ImageMedia) { |
||||||
|
vh.mItemLayout.setChecked(((ImageMedia) media).isSelected()); |
||||||
|
vh.mItemChecked.setTag(R.id.media_layout, vh.mItemLayout); |
||||||
|
vh.mItemChecked.setTag(media); |
||||||
|
vh.mItemChecked.setOnClickListener(mOnCheckListener); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long getItemId(int position) { |
||||||
|
return position; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getItemCount() { |
||||||
|
return mMedias.size() + mOffset; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOnCameraClickListener(View.OnClickListener onCameraClickListener) { |
||||||
|
mOnCameraClickListener = onCameraClickListener; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOnCheckedListener(OnMediaCheckedListener onCheckedListener) { |
||||||
|
mOnCheckedListener = onCheckedListener; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOnMediaClickListener(View.OnClickListener onMediaClickListener) { |
||||||
|
mOnMediaClickListener = onMediaClickListener; |
||||||
|
} |
||||||
|
|
||||||
|
public List<BaseMedia> getSelectedMedias() { |
||||||
|
return mSelectedMedias; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSelectedMedias(List<BaseMedia> selectedMedias) { |
||||||
|
if (selectedMedias == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mSelectedMedias.clear(); |
||||||
|
mSelectedMedias.addAll(selectedMedias); |
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
public void addAllData(@NonNull List<BaseMedia> data) { |
||||||
|
int oldSize = mMedias.size(); |
||||||
|
this.mMedias.addAll(data); |
||||||
|
int size = data.size(); |
||||||
|
notifyItemRangeInserted(oldSize, size); |
||||||
|
} |
||||||
|
|
||||||
|
public void clearData() { |
||||||
|
int size = mMedias.size(); |
||||||
|
this.mMedias.clear(); |
||||||
|
notifyItemRangeRemoved(0, size); |
||||||
|
} |
||||||
|
|
||||||
|
public List<BaseMedia> getAllMedias() { |
||||||
|
return mMedias; |
||||||
|
} |
||||||
|
|
||||||
|
private static class ImageViewHolder extends RecyclerView.ViewHolder { |
||||||
|
MediaItemLayout mItemLayout; |
||||||
|
View mItemChecked; |
||||||
|
|
||||||
|
ImageViewHolder(View itemView) { |
||||||
|
super(itemView); |
||||||
|
mItemLayout = (MediaItemLayout) itemView.findViewById(R.id.media_layout); |
||||||
|
mItemChecked = itemView.findViewById(R.id.media_item_check); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class CameraViewHolder extends RecyclerView.ViewHolder { |
||||||
|
View mCameraLayout; |
||||||
|
ImageView mCameraImg; |
||||||
|
|
||||||
|
CameraViewHolder(final View itemView) { |
||||||
|
super(itemView); |
||||||
|
mCameraLayout = itemView.findViewById(R.id.camera_layout); |
||||||
|
mCameraImg = (ImageView) itemView.findViewById(R.id.camera_img); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class OnCheckListener implements View.OnClickListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
MediaItemLayout itemLayout = (MediaItemLayout) v.getTag(R.id.media_layout); |
||||||
|
BaseMedia media = (BaseMedia) v.getTag(); |
||||||
|
if (mMediaConfig.getMode() == BoxingConfig.Mode.MULTI_IMG) { |
||||||
|
if (mOnCheckedListener != null) { |
||||||
|
mOnCheckedListener.onChecked(itemLayout, media); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public interface OnMediaCheckedListener { |
||||||
|
/** |
||||||
|
* In multi image mode, selecting a {@link BaseMedia} or undo. |
||||||
|
*/ |
||||||
|
void onChecked(View v, BaseMedia iMedia); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.ui; |
||||||
|
|
||||||
|
import android.app.Activity; |
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.TextView; |
||||||
|
|
||||||
|
import com.bilibili.boxing.AbsBoxingActivity; |
||||||
|
import com.bilibili.boxing.AbsBoxingViewFragment; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.appcompat.widget.Toolbar; |
||||||
|
|
||||||
|
/** |
||||||
|
* Default UI Activity for simplest usage. |
||||||
|
* A simple subclass of {@link AbsBoxingActivity}. Holding a {@link AbsBoxingViewFragment} to display medias. |
||||||
|
*/ |
||||||
|
public class BoxingActivity extends AbsBoxingActivity { |
||||||
|
private BoxingViewFragment mPickerFragment; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
setContentView(R.layout.activity_boxing); |
||||||
|
createToolbar(); |
||||||
|
setTitleTxt(getBoxingConfig()); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public AbsBoxingViewFragment onCreateBoxingView(ArrayList<BaseMedia> medias) { |
||||||
|
mPickerFragment = (BoxingViewFragment) getSupportFragmentManager().findFragmentByTag(BoxingViewFragment.TAG); |
||||||
|
if (mPickerFragment == null) { |
||||||
|
mPickerFragment = (BoxingViewFragment) BoxingViewFragment.newInstance().setSelectedBundle(medias); |
||||||
|
getSupportFragmentManager().beginTransaction().replace(R.id.content_layout, mPickerFragment, BoxingViewFragment.TAG).commit(); |
||||||
|
} |
||||||
|
return mPickerFragment; |
||||||
|
} |
||||||
|
|
||||||
|
private void createToolbar() { |
||||||
|
Toolbar bar = (Toolbar) findViewById(R.id.nav_top_bar); |
||||||
|
setSupportActionBar(bar); |
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
||||||
|
getSupportActionBar().setDisplayShowTitleEnabled(false); |
||||||
|
bar.setNavigationOnClickListener(new View.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
onBackPressed(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void setTitleTxt(BoxingConfig config) { |
||||||
|
TextView titleTxt = (TextView) findViewById(R.id.pick_album_txt); |
||||||
|
if (config.getMode() == BoxingConfig.Mode.VIDEO) { |
||||||
|
titleTxt.setText(R.string.boxing_video_title); |
||||||
|
titleTxt.setCompoundDrawables(null, null, null, null); |
||||||
|
return; |
||||||
|
} |
||||||
|
mPickerFragment.setTitleTxt(titleTxt); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onBoxingFinish(Intent intent, @Nullable List<BaseMedia> medias) { |
||||||
|
setResult(Activity.RESULT_OK, intent); |
||||||
|
finish(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
package com.bilibili.boxing_impl.ui; |
||||||
|
|
||||||
|
import android.os.Bundle; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.fragment.app.Fragment; |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by ChenSL on 2017/4/5. |
||||||
|
*/ |
||||||
|
|
||||||
|
public class BoxingBaseFragment extends Fragment { |
||||||
|
private boolean mNeedPendingUserVisibileHint; |
||||||
|
private boolean mLastUserVisibileHint; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) { |
||||||
|
super.onActivityCreated(savedInstanceState); |
||||||
|
if (mNeedPendingUserVisibileHint) { |
||||||
|
setUserVisibleCompat(mLastUserVisibileHint); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setUserVisibleHint(boolean isVisibleToUser) { |
||||||
|
super.setUserVisibleHint(isVisibleToUser); |
||||||
|
if (getActivity() == null) { |
||||||
|
mNeedPendingUserVisibileHint = true; |
||||||
|
mLastUserVisibileHint = isVisibleToUser; |
||||||
|
} else { |
||||||
|
setUserVisibleCompat(isVisibleToUser); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void setUserVisibleCompat(boolean userVisibleCompat) { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,142 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.ui; |
||||||
|
|
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.FrameLayout; |
||||||
|
import android.widget.ImageView; |
||||||
|
|
||||||
|
import com.bilibili.boxing.AbsBoxingActivity; |
||||||
|
import com.bilibili.boxing.AbsBoxingViewFragment; |
||||||
|
import com.bilibili.boxing.BoxingMediaLoader; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.appcompat.widget.Toolbar; |
||||||
|
|
||||||
|
/** |
||||||
|
* Default UI Activity for simplest usage, containing layout achieve {@link BottomSheetBehavior}. |
||||||
|
* Only support SINGLE_IMG and VIDEO Mode. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingBottomSheetActivity extends AbsBoxingActivity implements View.OnClickListener { |
||||||
|
private BottomSheetBehavior<FrameLayout> mBehavior; |
||||||
|
private ImageView mImage; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
setContentView(R.layout.activity_boxing_bottom_sheet); |
||||||
|
createToolbar(); |
||||||
|
|
||||||
|
FrameLayout bottomSheet = (FrameLayout) findViewById(R.id.content_layout); |
||||||
|
mBehavior = BottomSheetBehavior.from(bottomSheet); |
||||||
|
mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); |
||||||
|
|
||||||
|
mImage = (ImageView) findViewById(R.id.media_result); |
||||||
|
mImage.setOnClickListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public AbsBoxingViewFragment onCreateBoxingView(ArrayList<BaseMedia> medias) { |
||||||
|
BoxingBottomSheetFragment fragment = (BoxingBottomSheetFragment) getSupportFragmentManager().findFragmentByTag(BoxingBottomSheetFragment.TAG); |
||||||
|
if (fragment == null) { |
||||||
|
fragment = BoxingBottomSheetFragment.newInstance(); |
||||||
|
getSupportFragmentManager().beginTransaction() |
||||||
|
.add(R.id.content_layout, fragment, BoxingBottomSheetFragment.TAG).commit(); |
||||||
|
} |
||||||
|
return fragment; |
||||||
|
} |
||||||
|
|
||||||
|
private void createToolbar() { |
||||||
|
Toolbar bar = (Toolbar) findViewById(R.id.nav_top_bar); |
||||||
|
setSupportActionBar(bar); |
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
||||||
|
getSupportActionBar().setTitle(R.string.boxing_default_album); |
||||||
|
bar.setNavigationOnClickListener(new View.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
onBackPressed(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean hideBottomSheet() { |
||||||
|
if (mBehavior != null && mBehavior.getState() != BottomSheetBehavior.STATE_HIDDEN) { |
||||||
|
mBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean collapseBottomSheet() { |
||||||
|
if (mBehavior != null && mBehavior.getState() != BottomSheetBehavior.STATE_COLLAPSED) { |
||||||
|
mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private void toggleBottomSheet() { |
||||||
|
if (mBehavior == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (mBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) { |
||||||
|
mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); |
||||||
|
} else { |
||||||
|
mBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onBackPressed() { |
||||||
|
if (hideBottomSheet()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
super.onBackPressed(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
int id = v.getId(); |
||||||
|
if (id == R.id.media_result) { |
||||||
|
toggleBottomSheet(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onBoxingFinish(Intent intent, @Nullable List<BaseMedia> medias) { |
||||||
|
if (mImage != null && medias != null && !medias.isEmpty()) { |
||||||
|
ImageMedia imageMedia = (ImageMedia) medias.get(0); |
||||||
|
BoxingMediaLoader.getInstance().displayRaw(mImage, imageMedia.getPath(), 1080, 720, null); |
||||||
|
} |
||||||
|
hideBottomSheet(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,249 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.ui; |
||||||
|
|
||||||
|
import android.Manifest; |
||||||
|
import android.app.ProgressDialog; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ProgressBar; |
||||||
|
import android.widget.TextView; |
||||||
|
import android.widget.Toast; |
||||||
|
|
||||||
|
import com.bilibili.boxing.AbsBoxingViewFragment; |
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.presenter.PickerContract; |
||||||
|
import com.bilibili.boxing.utils.BoxingFileHelper; |
||||||
|
import com.bilibili.boxing_impl.adapter.BoxingMediaAdapter; |
||||||
|
import com.bilibili.boxing_impl.view.HackyGridLayoutManager; |
||||||
|
import com.bilibili.boxing_impl.view.SpacesItemDecoration; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.recyclerview.widget.GridLayoutManager; |
||||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||||
|
|
||||||
|
/** |
||||||
|
* the most easy to implement {@link PickerContract.View} to show medias with google's Bottom Sheet </br> |
||||||
|
* for simplest purpose, it only support SINGLE_IMG and VIDEO Mode. |
||||||
|
* for MULTI_IMG mode, use {@link BoxingViewFragment} instead. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingBottomSheetFragment extends AbsBoxingViewFragment implements View.OnClickListener { |
||||||
|
public static final String TAG = "com.bilibili.boxing_impl.ui.BoxingBottomSheetFragment"; |
||||||
|
|
||||||
|
private static final int GRID_COUNT = 3; |
||||||
|
|
||||||
|
private boolean mIsCamera; |
||||||
|
|
||||||
|
private BoxingMediaAdapter mMediaAdapter; |
||||||
|
private ProgressDialog mDialog; |
||||||
|
private RecyclerView mRecycleView; |
||||||
|
private TextView mEmptyTxt; |
||||||
|
private ProgressBar mLoadingView; |
||||||
|
|
||||||
|
public static BoxingBottomSheetFragment newInstance() { |
||||||
|
return new BoxingBottomSheetFragment(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
mMediaAdapter = new BoxingMediaAdapter(getActivity()); |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
@Override |
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |
||||||
|
return inflater.inflate(R.layout.fragment_boxing_bottom_sheet, container, false); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) { |
||||||
|
super.onViewCreated(view, savedInstanceState); |
||||||
|
mEmptyTxt = (TextView) view.findViewById(R.id.empty_txt); |
||||||
|
mRecycleView = (RecyclerView) view.findViewById(R.id.media_recycleview); |
||||||
|
mRecycleView.setHasFixedSize(true); |
||||||
|
mLoadingView = (ProgressBar) view.findViewById(R.id.loading); |
||||||
|
GridLayoutManager gridLayoutManager = new HackyGridLayoutManager(getActivity(), GRID_COUNT); |
||||||
|
gridLayoutManager.setSmoothScrollbarEnabled(true); |
||||||
|
mRecycleView.setLayoutManager(gridLayoutManager); |
||||||
|
mRecycleView.addItemDecoration(new SpacesItemDecoration(getResources().getDimensionPixelOffset(R.dimen.boxing_media_margin), GRID_COUNT)); |
||||||
|
mRecycleView.setAdapter(mMediaAdapter); |
||||||
|
mRecycleView.addOnScrollListener(new ScrollListener()); |
||||||
|
mMediaAdapter.setOnMediaClickListener(new OnMediaClickListener()); |
||||||
|
mMediaAdapter.setOnCameraClickListener(new OnCameraClickListener()); |
||||||
|
view.findViewById(R.id.finish_txt).setOnClickListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraActivityResult(int requestCode, int resultCode) { |
||||||
|
showProgressDialog(); |
||||||
|
super.onCameraActivityResult(requestCode, resultCode); |
||||||
|
} |
||||||
|
|
||||||
|
private void showProgressDialog() { |
||||||
|
if (mDialog == null) { |
||||||
|
mDialog = new ProgressDialog(getActivity()); |
||||||
|
mDialog.setIndeterminate(true); |
||||||
|
mDialog.setMessage(getString(R.string.boxing_handling)); |
||||||
|
} |
||||||
|
if (!mDialog.isShowing()) { |
||||||
|
mDialog.show(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void dismissProgressDialog() { |
||||||
|
if (mDialog != null && mDialog.isShowing()) { |
||||||
|
mDialog.hide(); |
||||||
|
mDialog.dismiss(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void showMedia(List<BaseMedia> medias, int count) { |
||||||
|
if (medias == null || isEmptyData(medias) |
||||||
|
&& isEmptyData(mMediaAdapter.getAllMedias())) { |
||||||
|
showEmptyData(); |
||||||
|
return; |
||||||
|
} |
||||||
|
showData(); |
||||||
|
mMediaAdapter.addAllData(medias); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isEmptyData(List<BaseMedia> medias) { |
||||||
|
return medias.isEmpty() && !BoxingManager.getInstance().getBoxingConfig().isNeedCamera(); |
||||||
|
} |
||||||
|
|
||||||
|
private void showEmptyData() { |
||||||
|
mEmptyTxt.setVisibility(View.VISIBLE); |
||||||
|
mRecycleView.setVisibility(View.GONE); |
||||||
|
mLoadingView.setVisibility(View.GONE); |
||||||
|
} |
||||||
|
|
||||||
|
private void showData() { |
||||||
|
mLoadingView.setVisibility(View.GONE); |
||||||
|
mEmptyTxt.setVisibility(View.GONE); |
||||||
|
mRecycleView.setVisibility(View.VISIBLE); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraFinish(BaseMedia media) { |
||||||
|
dismissProgressDialog(); |
||||||
|
mIsCamera = false; |
||||||
|
if (media != null) { |
||||||
|
List<BaseMedia> selectedMedias = mMediaAdapter.getSelectedMedias(); |
||||||
|
selectedMedias.add(media); |
||||||
|
BoxingBottomSheetFragment.this.onFinish(selectedMedias); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraError() { |
||||||
|
mIsCamera = false; |
||||||
|
dismissProgressDialog(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void startLoading() { |
||||||
|
loadMedias(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRequestPermissionError(String[] permissions, Exception e) { |
||||||
|
if (permissions.length > 0) { |
||||||
|
if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { |
||||||
|
showEmptyData(); |
||||||
|
Toast.makeText(getContext(), R.string.boxing_storage_permission_deny, Toast.LENGTH_SHORT).show(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRequestPermissionSuc(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
||||||
|
if (permissions[0].equals(STORAGE_PERMISSIONS[0])) { |
||||||
|
startLoading(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void clearMedia() { |
||||||
|
mMediaAdapter.clearData(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
int id = v.getId(); |
||||||
|
if (R.id.finish_txt == id) { |
||||||
|
onFinish(null); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private class OnMediaClickListener implements View.OnClickListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
ArrayList<BaseMedia> iMedias = new ArrayList<>(); |
||||||
|
BaseMedia media = (BaseMedia) v.getTag(); |
||||||
|
iMedias.add(media); |
||||||
|
onFinish(iMedias); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class OnCameraClickListener implements View.OnClickListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
if (!mIsCamera) { |
||||||
|
mIsCamera = true; |
||||||
|
startCamera(getActivity(), BoxingBottomSheetFragment.this, BoxingFileHelper.DEFAULT_SUB_DIR); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class ScrollListener extends RecyclerView.OnScrollListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
||||||
|
final int childCount = recyclerView.getChildCount(); |
||||||
|
if (childCount > 0) { |
||||||
|
View lastChild = recyclerView.getChildAt(childCount - 1); |
||||||
|
RecyclerView.Adapter outerAdapter = recyclerView.getAdapter(); |
||||||
|
int lastVisible = recyclerView.getChildAdapterPosition(lastChild); |
||||||
|
if (lastVisible == outerAdapter.getItemCount() - 1 && hasNextPage() && canLoadNextPage()) { |
||||||
|
onLoadNextPage(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,191 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.ui; |
||||||
|
|
||||||
|
import android.app.Activity; |
||||||
|
import android.graphics.Point; |
||||||
|
import android.graphics.drawable.Drawable; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.util.DisplayMetrics; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ProgressBar; |
||||||
|
|
||||||
|
import com.bilibili.boxing.AbsBoxingViewActivity; |
||||||
|
import com.bilibili.boxing.loader.IBoxingCallback; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.utils.BoxingLog; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.lang.ref.WeakReference; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import uk.co.senab.photoview.PhotoView; |
||||||
|
import uk.co.senab.photoview.PhotoViewAttacher; |
||||||
|
|
||||||
|
/** |
||||||
|
* show raw image with the control of finger gesture. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingRawImageFragment extends BoxingBaseFragment { |
||||||
|
private static final String BUNDLE_IMAGE = "com.bilibili.boxing_impl.ui.BoxingRawImageFragment.image"; |
||||||
|
private static final int MAX_SCALE = 15; |
||||||
|
private static final long MAX_IMAGE1 = 1024 * 1024L; |
||||||
|
private static final long MAX_IMAGE2 = 4 * MAX_IMAGE1; |
||||||
|
|
||||||
|
private PhotoView mImageView; |
||||||
|
private ProgressBar mProgress; |
||||||
|
private ImageMedia mMedia; |
||||||
|
private PhotoViewAttacher mAttacher; |
||||||
|
|
||||||
|
public static BoxingRawImageFragment newInstance(@NonNull ImageMedia image) { |
||||||
|
BoxingRawImageFragment fragment = new BoxingRawImageFragment(); |
||||||
|
Bundle args = new Bundle(); |
||||||
|
args.putParcelable(BUNDLE_IMAGE, image); |
||||||
|
fragment.setArguments(args); |
||||||
|
return fragment; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
mMedia = getArguments().getParcelable(BUNDLE_IMAGE); |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
@Override |
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
||||||
|
return inflater.inflate(R.layout.fragment_boxing_raw_image, container, false); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) { |
||||||
|
super.onViewCreated(view, savedInstanceState); |
||||||
|
mProgress = (ProgressBar) view.findViewById(R.id.loading); |
||||||
|
mImageView = (PhotoView) view.findViewById(R.id.photo_view); |
||||||
|
mAttacher = new PhotoViewAttacher(mImageView); |
||||||
|
mAttacher.setRotatable(true); |
||||||
|
mAttacher.setToRightAngle(true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void setUserVisibleCompat(boolean isVisibleToUser) { |
||||||
|
if (isVisibleToUser) { |
||||||
|
Point point = getResizePointer(mMedia.getSize()); |
||||||
|
((AbsBoxingViewActivity) getActivity()).loadRawImage(mImageView, mMedia.getPath(), point.x, point.y, new BoxingCallback(this)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* resize the image or not according to size. |
||||||
|
* |
||||||
|
* @param size the size of image |
||||||
|
*/ |
||||||
|
private Point getResizePointer(long size) { |
||||||
|
DisplayMetrics metrics = getResources().getDisplayMetrics(); |
||||||
|
Point point = new Point(metrics.widthPixels, metrics.heightPixels); |
||||||
|
if (size >= MAX_IMAGE2) { |
||||||
|
point.x >>= 2; |
||||||
|
point.y >>= 2; |
||||||
|
} else if (size >= MAX_IMAGE1) { |
||||||
|
point.x >>= 1; |
||||||
|
point.y >>= 1; |
||||||
|
} else if (size > 0) { |
||||||
|
// avoid some images do not have a size.
|
||||||
|
point.x = 0; |
||||||
|
point.y = 0; |
||||||
|
} |
||||||
|
return point; |
||||||
|
} |
||||||
|
|
||||||
|
private void dismissProgressDialog() { |
||||||
|
if (mProgress != null) { |
||||||
|
mProgress.setVisibility(View.GONE); |
||||||
|
} |
||||||
|
BoxingViewActivity activity = getThisActivity(); |
||||||
|
if (activity != null && activity.mProgressBar != null) { |
||||||
|
activity.mProgressBar.setVisibility(View.GONE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private BoxingViewActivity getThisActivity() { |
||||||
|
Activity activity = getActivity(); |
||||||
|
if (activity instanceof BoxingViewActivity) { |
||||||
|
return (BoxingViewActivity) activity; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDestroyView() { |
||||||
|
super.onDestroyView(); |
||||||
|
if (mAttacher != null) { |
||||||
|
mAttacher.cleanup(); |
||||||
|
mAttacher = null; |
||||||
|
mImageView = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class BoxingCallback implements IBoxingCallback { |
||||||
|
private WeakReference<BoxingRawImageFragment> mWr; |
||||||
|
|
||||||
|
BoxingCallback(BoxingRawImageFragment fragment) { |
||||||
|
mWr = new WeakReference<>(fragment); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSuccess() { |
||||||
|
if (mWr.get() == null || mWr.get().mImageView == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mWr.get().dismissProgressDialog(); |
||||||
|
Drawable drawable = mWr.get().mImageView.getDrawable(); |
||||||
|
PhotoViewAttacher attacher = mWr.get().mAttacher; |
||||||
|
if (attacher != null) { |
||||||
|
if (drawable.getIntrinsicHeight() > (drawable.getIntrinsicWidth() << 2)) { |
||||||
|
// handle the super height image.
|
||||||
|
int scale = drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth(); |
||||||
|
scale = Math.min(MAX_SCALE, scale); |
||||||
|
attacher.setMaximumScale(scale); |
||||||
|
attacher.setScale(scale, true); |
||||||
|
} |
||||||
|
attacher.update(); |
||||||
|
} |
||||||
|
BoxingViewActivity activity = mWr.get().getThisActivity(); |
||||||
|
if (activity != null && activity.mGallery != null) { |
||||||
|
activity.mGallery.setVisibility(View.VISIBLE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFail(Throwable t) { |
||||||
|
if (mWr.get() == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
BoxingLog.d(t != null ? t.getMessage() : "load raw image error."); |
||||||
|
mWr.get().dismissProgressDialog(); |
||||||
|
mWr.get().mImageView.setImageResource(R.drawable.ic_boxing_broken_image); |
||||||
|
if (mWr.get().mAttacher != null) { |
||||||
|
mWr.get().mAttacher.update(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,333 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.ui; |
||||||
|
|
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.view.Menu; |
||||||
|
import android.view.MenuItem; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.Button; |
||||||
|
import android.widget.ProgressBar; |
||||||
|
import android.widget.Toast; |
||||||
|
|
||||||
|
import com.bilibili.boxing.AbsBoxingViewActivity; |
||||||
|
import com.bilibili.boxing.Boxing; |
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.model.task.IMediaTask; |
||||||
|
import com.bilibili.boxing_impl.BoxingResHelper; |
||||||
|
import com.bilibili.boxing_impl.view.HackyViewPager; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.appcompat.widget.Toolbar; |
||||||
|
import androidx.fragment.app.Fragment; |
||||||
|
import androidx.fragment.app.FragmentManager; |
||||||
|
import androidx.fragment.app.FragmentStatePagerAdapter; |
||||||
|
import androidx.viewpager.widget.ViewPager; |
||||||
|
|
||||||
|
/** |
||||||
|
* An Activity to show raw image by holding {@link BoxingViewFragment}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingViewActivity extends AbsBoxingViewActivity { |
||||||
|
public static final String EXTRA_TYPE_BACK = "com.bilibili.boxing_impl.ui.BoxingViewActivity.type_back"; |
||||||
|
|
||||||
|
HackyViewPager mGallery; |
||||||
|
ProgressBar mProgressBar; |
||||||
|
|
||||||
|
private boolean mNeedEdit; |
||||||
|
private boolean mNeedLoading; |
||||||
|
private boolean mFinishLoading; |
||||||
|
private boolean mNeedAllCount = true; |
||||||
|
private int mCurrentPage; |
||||||
|
private int mTotalCount; |
||||||
|
private int mStartPos; |
||||||
|
private int mPos; |
||||||
|
private int mMaxCount; |
||||||
|
|
||||||
|
private String mAlbumId; |
||||||
|
private Toolbar mToolbar; |
||||||
|
private ImagesAdapter mAdapter; |
||||||
|
private ImageMedia mCurrentImageItem; |
||||||
|
private Button mOkBtn; |
||||||
|
private ArrayList<BaseMedia> mImages; |
||||||
|
private ArrayList<BaseMedia> mSelectedImages; |
||||||
|
private MenuItem mSelectedMenuItem; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
setContentView(R.layout.activity_boxing_view); |
||||||
|
createToolbar(); |
||||||
|
initData(); |
||||||
|
initView(); |
||||||
|
startLoading(); |
||||||
|
} |
||||||
|
|
||||||
|
private void createToolbar() { |
||||||
|
mToolbar = (Toolbar) findViewById(R.id.nav_top_bar); |
||||||
|
setSupportActionBar(mToolbar); |
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
||||||
|
mToolbar.setNavigationOnClickListener(new View.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
onBackPressed(); |
||||||
|
} |
||||||
|
}); |
||||||
|
getSupportActionBar().setDisplayShowTitleEnabled(false); |
||||||
|
} |
||||||
|
|
||||||
|
private void initData() { |
||||||
|
mSelectedImages = getSelectedImages(); |
||||||
|
mAlbumId = getAlbumId(); |
||||||
|
mStartPos = getStartPos(); |
||||||
|
mNeedLoading = BoxingManager.getInstance().getBoxingConfig().isNeedLoading(); |
||||||
|
mNeedEdit = BoxingManager.getInstance().getBoxingConfig().isNeedEdit(); |
||||||
|
mMaxCount = getMaxCount(); |
||||||
|
mImages = new ArrayList<>(); |
||||||
|
if (!mNeedLoading && mSelectedImages != null) { |
||||||
|
mImages.addAll(mSelectedImages); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void initView() { |
||||||
|
mAdapter = new ImagesAdapter(getSupportFragmentManager()); |
||||||
|
mOkBtn = (Button) findViewById(R.id.image_items_ok); |
||||||
|
mGallery = (HackyViewPager) findViewById(R.id.pager); |
||||||
|
mProgressBar = (ProgressBar) findViewById(R.id.loading); |
||||||
|
mGallery.setAdapter(mAdapter); |
||||||
|
mGallery.addOnPageChangeListener(new OnPagerChangeListener()); |
||||||
|
if (!mNeedEdit) { |
||||||
|
View chooseLayout = findViewById(R.id.item_choose_layout); |
||||||
|
chooseLayout.setVisibility(View.GONE); |
||||||
|
} else { |
||||||
|
setOkTextNumber(); |
||||||
|
mOkBtn.setOnClickListener(new View.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
finishByBackPressed(false); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void setOkTextNumber() { |
||||||
|
if (mNeedEdit) { |
||||||
|
int selectedSize = mSelectedImages.size(); |
||||||
|
int size = Math.max(mSelectedImages.size(), mMaxCount); |
||||||
|
mOkBtn.setText(getString(R.string.boxing_image_preview_ok_fmt, String.valueOf(selectedSize) |
||||||
|
, String.valueOf(size))); |
||||||
|
mOkBtn.setEnabled(selectedSize > 0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void finishByBackPressed(boolean value) { |
||||||
|
Intent intent = new Intent(); |
||||||
|
intent.putParcelableArrayListExtra(Boxing.EXTRA_SELECTED_MEDIA, mSelectedImages); |
||||||
|
intent.putExtra(EXTRA_TYPE_BACK, value); |
||||||
|
setResult(RESULT_OK, intent); |
||||||
|
finish(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onCreateOptionsMenu(Menu menu) { |
||||||
|
super.onCreateOptionsMenu(menu); |
||||||
|
if (mNeedEdit) { |
||||||
|
getMenuInflater().inflate(R.menu.activity_boxing_image_viewer, menu); |
||||||
|
mSelectedMenuItem = menu.findItem(R.id.menu_image_item_selected); |
||||||
|
if (mCurrentImageItem != null) { |
||||||
|
setMenuIcon(mCurrentImageItem.isSelected()); |
||||||
|
} else { |
||||||
|
setMenuIcon(false); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onOptionsItemSelected(MenuItem item) { |
||||||
|
int id = item.getItemId(); |
||||||
|
if (id == R.id.menu_image_item_selected) { |
||||||
|
if (mCurrentImageItem == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (mSelectedImages.size() >= mMaxCount && !mCurrentImageItem.isSelected()) { |
||||||
|
String warning = getString(R.string.boxing_max_image_over_fmt, mMaxCount); |
||||||
|
Toast.makeText(this, warning, Toast.LENGTH_SHORT).show(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (mCurrentImageItem.isSelected()) { |
||||||
|
cancelImage(); |
||||||
|
} else { |
||||||
|
if (!mSelectedImages.contains(mCurrentImageItem)) { |
||||||
|
if (mCurrentImageItem.isGifOverSize()) { |
||||||
|
Toast.makeText(getApplicationContext(), R.string.boxing_gif_too_big, Toast.LENGTH_SHORT).show(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
mCurrentImageItem.setSelected(true); |
||||||
|
mSelectedImages.add(mCurrentImageItem); |
||||||
|
} |
||||||
|
} |
||||||
|
setOkTextNumber(); |
||||||
|
setMenuIcon(mCurrentImageItem.isSelected()); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item); |
||||||
|
} |
||||||
|
|
||||||
|
private void cancelImage() { |
||||||
|
if (mSelectedImages.contains(mCurrentImageItem)) { |
||||||
|
mSelectedImages.remove(mCurrentImageItem); |
||||||
|
} |
||||||
|
mCurrentImageItem.setSelected(false); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private void setMenuIcon(boolean isSelected) { |
||||||
|
if (mNeedEdit) { |
||||||
|
mSelectedMenuItem.setIcon(isSelected ? BoxingResHelper.getMediaCheckedRes(): BoxingResHelper.getMediaUncheckedRes()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void startLoading() { |
||||||
|
if (!mNeedLoading) { |
||||||
|
mCurrentImageItem = (ImageMedia) mSelectedImages.get(mStartPos); |
||||||
|
mToolbar.setTitle(getString(R.string.boxing_image_preview_title_fmt, String.valueOf(mStartPos + 1) |
||||||
|
, String.valueOf(mSelectedImages.size()))); |
||||||
|
mProgressBar.setVisibility(View.GONE); |
||||||
|
mGallery.setVisibility(View.VISIBLE); |
||||||
|
mAdapter.setMedias(mImages); |
||||||
|
if (mStartPos > 0 && mStartPos < mSelectedImages.size()) { |
||||||
|
mGallery.setCurrentItem(mStartPos, false); |
||||||
|
} |
||||||
|
} else { |
||||||
|
loadMedia(mAlbumId, mStartPos, mCurrentPage); |
||||||
|
mAdapter.setMedias(mImages); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void loadMedia(String albumId, int startPos, int page) { |
||||||
|
this.mPos = startPos; |
||||||
|
loadMedias(page, albumId); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void showMedia(@Nullable List<BaseMedia> medias, int totalCount) { |
||||||
|
if (medias == null || totalCount <= 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mImages.addAll(medias); |
||||||
|
mAdapter.notifyDataSetChanged(); |
||||||
|
checkSelectedMedia(mImages, mSelectedImages); |
||||||
|
setupGallery(); |
||||||
|
|
||||||
|
if (mToolbar != null && mNeedAllCount) { |
||||||
|
mToolbar.setTitle(getString(R.string.boxing_image_preview_title_fmt, |
||||||
|
String.valueOf(++mPos), String.valueOf(totalCount))); |
||||||
|
mNeedAllCount = false; |
||||||
|
} |
||||||
|
loadOtherPagesInAlbum(totalCount); |
||||||
|
} |
||||||
|
|
||||||
|
private void setupGallery() { |
||||||
|
int startPos = mStartPos; |
||||||
|
if (mGallery == null || startPos < 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (startPos < mImages.size() && !mFinishLoading) { |
||||||
|
mGallery.setCurrentItem(mStartPos, false); |
||||||
|
mCurrentImageItem = (ImageMedia) mImages.get(startPos); |
||||||
|
mProgressBar.setVisibility(View.GONE); |
||||||
|
mGallery.setVisibility(View.VISIBLE); |
||||||
|
mFinishLoading = true; |
||||||
|
invalidateOptionsMenu(); |
||||||
|
} else if (startPos >= mImages.size()) { |
||||||
|
mProgressBar.setVisibility(View.VISIBLE); |
||||||
|
mGallery.setVisibility(View.GONE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void loadOtherPagesInAlbum(int totalCount) { |
||||||
|
mTotalCount = totalCount; |
||||||
|
if (mCurrentPage <= (mTotalCount / IMediaTask.PAGE_LIMIT)) { |
||||||
|
mCurrentPage++; |
||||||
|
loadMedia(mAlbumId, mStartPos, mCurrentPage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onSaveInstanceState(Bundle outState) { |
||||||
|
if (mSelectedImages != null) { |
||||||
|
outState.putParcelableArrayList(Boxing.EXTRA_SELECTED_MEDIA, mSelectedImages); |
||||||
|
} |
||||||
|
outState.putString(Boxing.EXTRA_ALBUM_ID, mAlbumId); |
||||||
|
super.onSaveInstanceState(outState); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onBackPressed() { |
||||||
|
finishByBackPressed(true); |
||||||
|
} |
||||||
|
|
||||||
|
private class ImagesAdapter extends FragmentStatePagerAdapter { |
||||||
|
private ArrayList<BaseMedia> mMedias; |
||||||
|
|
||||||
|
ImagesAdapter(FragmentManager fm) { |
||||||
|
super(fm); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Fragment getItem(int i) { |
||||||
|
return BoxingRawImageFragment.newInstance((ImageMedia) mMedias.get(i)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getCount() { |
||||||
|
return mMedias == null ? 0 : mMedias.size(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setMedias(ArrayList<BaseMedia> medias) { |
||||||
|
this.mMedias = medias; |
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class OnPagerChangeListener extends ViewPager.SimpleOnPageChangeListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onPageSelected(int position) { |
||||||
|
if (mToolbar != null && position < mImages.size()) { |
||||||
|
mToolbar.setTitle(getString(R.string.boxing_image_preview_title_fmt, String.valueOf(position + 1) |
||||||
|
, mNeedLoading ? String.valueOf(mTotalCount) : String.valueOf(mImages.size()))); |
||||||
|
mCurrentImageItem = (ImageMedia) mImages.get(position); |
||||||
|
invalidateOptionsMenu(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,526 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.ui; |
||||||
|
|
||||||
|
import android.Manifest; |
||||||
|
import android.app.Activity; |
||||||
|
import android.app.ProgressDialog; |
||||||
|
import android.content.Intent; |
||||||
|
import android.graphics.drawable.ColorDrawable; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.Button; |
||||||
|
import android.widget.PopupWindow; |
||||||
|
import android.widget.ProgressBar; |
||||||
|
import android.widget.TextView; |
||||||
|
import android.widget.Toast; |
||||||
|
|
||||||
|
import com.bilibili.boxing.AbsBoxingViewFragment; |
||||||
|
import com.bilibili.boxing.Boxing; |
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.utils.BoxingFileHelper; |
||||||
|
import com.bilibili.boxing_impl.WindowManagerHelper; |
||||||
|
import com.bilibili.boxing_impl.adapter.BoxingAlbumAdapter; |
||||||
|
import com.bilibili.boxing_impl.adapter.BoxingMediaAdapter; |
||||||
|
import com.bilibili.boxing_impl.view.HackyGridLayoutManager; |
||||||
|
import com.bilibili.boxing_impl.view.MediaItemLayout; |
||||||
|
import com.bilibili.boxing_impl.view.SpacesItemDecoration; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.core.content.ContextCompat; |
||||||
|
import androidx.recyclerview.widget.GridLayoutManager; |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager; |
||||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||||
|
|
||||||
|
/** |
||||||
|
* A full implement for {@link com.bilibili.boxing.presenter.PickerContract.View} supporting all the mode |
||||||
|
* in {@link BoxingConfig.Mode}. |
||||||
|
* use this to pick the picture. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingViewFragment extends AbsBoxingViewFragment implements View.OnClickListener { |
||||||
|
public static final String TAG = "com.bilibili.boxing_impl.ui.BoxingViewFragment"; |
||||||
|
private static final int IMAGE_PREVIEW_REQUEST_CODE = 9086; |
||||||
|
private static final int IMAGE_CROP_REQUEST_CODE = 9087; |
||||||
|
|
||||||
|
private static final int GRID_COUNT = 3; |
||||||
|
|
||||||
|
private boolean mIsPreview; |
||||||
|
private boolean mIsCamera; |
||||||
|
|
||||||
|
private Button mPreBtn; |
||||||
|
private Button mOkBtn; |
||||||
|
private RecyclerView mRecycleView; |
||||||
|
private BoxingMediaAdapter mMediaAdapter; |
||||||
|
private BoxingAlbumAdapter mAlbumWindowAdapter; |
||||||
|
private ProgressDialog mDialog; |
||||||
|
private TextView mEmptyTxt; |
||||||
|
private TextView mTitleTxt; |
||||||
|
private PopupWindow mAlbumPopWindow; |
||||||
|
private ProgressBar mLoadingView; |
||||||
|
|
||||||
|
private int mMaxCount; |
||||||
|
|
||||||
|
public static BoxingViewFragment newInstance() { |
||||||
|
return new BoxingViewFragment(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreateWithSelectedMedias(Bundle savedInstanceState, @Nullable List<BaseMedia> selectedMedias) { |
||||||
|
mAlbumWindowAdapter = new BoxingAlbumAdapter(getContext()); |
||||||
|
mMediaAdapter = new BoxingMediaAdapter(getContext()); |
||||||
|
mMediaAdapter.setSelectedMedias(selectedMedias); |
||||||
|
mMaxCount = getMaxCount(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void startLoading() { |
||||||
|
loadMedias(); |
||||||
|
loadAlbum(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRequestPermissionError(String[] permissions, Exception e) { |
||||||
|
if (permissions.length > 0) { |
||||||
|
if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { |
||||||
|
Toast.makeText(getContext(), R.string.boxing_storage_permission_deny, Toast.LENGTH_SHORT).show(); |
||||||
|
showEmptyData(); |
||||||
|
} else if (permissions[0].equals(Manifest.permission.CAMERA)){ |
||||||
|
Toast.makeText(getContext(), R.string.boxing_camera_permission_deny, Toast.LENGTH_SHORT).show(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRequestPermissionSuc(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
||||||
|
if (permissions[0].equals(STORAGE_PERMISSIONS[0])) { |
||||||
|
startLoading(); |
||||||
|
} else if (permissions[0].equals(CAMERA_PERMISSIONS[0])) { |
||||||
|
startCamera(getActivity(), this, null); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
@Override |
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
||||||
|
return inflater.inflate(R.layout.fragmant_boxing_view, container, false); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { |
||||||
|
initViews(view); |
||||||
|
super.onViewCreated(view, savedInstanceState); |
||||||
|
} |
||||||
|
|
||||||
|
private void initViews(View view) { |
||||||
|
mEmptyTxt = (TextView) view.findViewById(R.id.empty_txt); |
||||||
|
mRecycleView = (RecyclerView) view.findViewById(R.id.media_recycleview); |
||||||
|
mRecycleView.setHasFixedSize(true); |
||||||
|
mLoadingView = (ProgressBar) view.findViewById(R.id.loading); |
||||||
|
initRecycleView(); |
||||||
|
|
||||||
|
boolean isMultiImageMode = BoxingManager.getInstance().getBoxingConfig().isMultiImageMode(); |
||||||
|
View multiImageLayout = view.findViewById(R.id.multi_picker_layout); |
||||||
|
multiImageLayout.setVisibility(isMultiImageMode ? View.VISIBLE : View.GONE); |
||||||
|
if (isMultiImageMode) { |
||||||
|
mPreBtn = (Button) view.findViewById(R.id.choose_preview_btn); |
||||||
|
mOkBtn = (Button) view.findViewById(R.id.choose_ok_btn); |
||||||
|
|
||||||
|
mPreBtn.setOnClickListener(this); |
||||||
|
mOkBtn.setOnClickListener(this); |
||||||
|
updateMultiPickerLayoutState(mMediaAdapter.getSelectedMedias()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void initRecycleView() { |
||||||
|
GridLayoutManager gridLayoutManager = new HackyGridLayoutManager(getActivity(), GRID_COUNT); |
||||||
|
gridLayoutManager.setSmoothScrollbarEnabled(true); |
||||||
|
mRecycleView.setLayoutManager(gridLayoutManager); |
||||||
|
mRecycleView.addItemDecoration(new SpacesItemDecoration(getResources().getDimensionPixelOffset(R.dimen.boxing_media_margin), GRID_COUNT)); |
||||||
|
mMediaAdapter.setOnCameraClickListener(new OnCameraClickListener()); |
||||||
|
mMediaAdapter.setOnCheckedListener(new OnMediaCheckedListener()); |
||||||
|
mMediaAdapter.setOnMediaClickListener(new OnMediaClickListener()); |
||||||
|
mRecycleView.setAdapter(mMediaAdapter); |
||||||
|
mRecycleView.addOnScrollListener(new ScrollListener()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void showMedia(@Nullable List<BaseMedia> medias, int allCount) { |
||||||
|
if (medias == null || isEmptyData(medias) |
||||||
|
&& isEmptyData(mMediaAdapter.getAllMedias())) { |
||||||
|
showEmptyData(); |
||||||
|
return; |
||||||
|
} |
||||||
|
showData(); |
||||||
|
mMediaAdapter.addAllData(medias); |
||||||
|
checkSelectedMedia(medias, mMediaAdapter.getSelectedMedias()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isEmptyData(List<BaseMedia> medias) { |
||||||
|
return medias.isEmpty() && !BoxingManager.getInstance().getBoxingConfig().isNeedCamera(); |
||||||
|
} |
||||||
|
|
||||||
|
private void showEmptyData() { |
||||||
|
mLoadingView.setVisibility(View.GONE); |
||||||
|
mEmptyTxt.setVisibility(View.VISIBLE); |
||||||
|
mRecycleView.setVisibility(View.GONE); |
||||||
|
} |
||||||
|
|
||||||
|
private void showData() { |
||||||
|
mLoadingView.setVisibility(View.GONE); |
||||||
|
mEmptyTxt.setVisibility(View.GONE); |
||||||
|
mRecycleView.setVisibility(View.VISIBLE); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void showAlbum(@Nullable List<AlbumEntity> albums) { |
||||||
|
if ((albums == null || albums.isEmpty()) |
||||||
|
&& mTitleTxt != null) { |
||||||
|
mTitleTxt.setCompoundDrawables(null, null, null, null); |
||||||
|
mTitleTxt.setOnClickListener(null); |
||||||
|
return; |
||||||
|
} |
||||||
|
mAlbumWindowAdapter.addAllData(albums); |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingMediaAdapter getMediaAdapter() { |
||||||
|
return mMediaAdapter; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void clearMedia() { |
||||||
|
mMediaAdapter.clearData(); |
||||||
|
} |
||||||
|
|
||||||
|
private void updateMultiPickerLayoutState(List<BaseMedia> medias) { |
||||||
|
updateOkBtnState(medias); |
||||||
|
updatePreviewBtnState(medias); |
||||||
|
} |
||||||
|
|
||||||
|
private void updatePreviewBtnState(List<BaseMedia> medias) { |
||||||
|
if (mPreBtn == null || medias == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
boolean enabled = medias.size() > 0 && medias.size() <= mMaxCount; |
||||||
|
mPreBtn.setEnabled(enabled); |
||||||
|
} |
||||||
|
|
||||||
|
private void updateOkBtnState(List<BaseMedia> medias) { |
||||||
|
if (mOkBtn == null || medias == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
boolean enabled = medias.size() > 0 && medias.size() <= mMaxCount; |
||||||
|
mOkBtn.setEnabled(enabled); |
||||||
|
mOkBtn.setText(enabled ? getString(R.string.boxing_image_select_ok_fmt, String.valueOf(medias.size()) |
||||||
|
, String.valueOf(mMaxCount)) : getString(R.string.boxing_ok)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraFinish(BaseMedia media) { |
||||||
|
dismissProgressDialog(); |
||||||
|
mIsCamera = false; |
||||||
|
if (media == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (hasCropBehavior()) { |
||||||
|
startCrop(media, IMAGE_CROP_REQUEST_CODE); |
||||||
|
} else if (mMediaAdapter != null && mMediaAdapter.getSelectedMedias() != null) { |
||||||
|
List<BaseMedia> selectedMedias = mMediaAdapter.getSelectedMedias(); |
||||||
|
selectedMedias.add(media); |
||||||
|
onFinish(selectedMedias); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraError() { |
||||||
|
mIsCamera = false; |
||||||
|
dismissProgressDialog(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
int id = v.getId(); |
||||||
|
if (id == R.id.choose_ok_btn) { |
||||||
|
onFinish(mMediaAdapter.getSelectedMedias()); |
||||||
|
} else if (id == R.id.choose_preview_btn) { |
||||||
|
if (!mIsPreview) { |
||||||
|
mIsPreview = true; |
||||||
|
ArrayList<BaseMedia> medias = (ArrayList<BaseMedia>) mMediaAdapter.getSelectedMedias(); |
||||||
|
Boxing.get().withIntent(getActivity(), BoxingViewActivity.class, medias) |
||||||
|
.start(this, BoxingViewFragment.IMAGE_PREVIEW_REQUEST_CODE, BoxingConfig.ViewMode.PRE_EDIT); |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) { |
||||||
|
super.onActivityResult(requestCode, resultCode, data); |
||||||
|
if (data == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_PREVIEW_REQUEST_CODE) { |
||||||
|
mIsPreview = false; |
||||||
|
boolean isBackClick = data.getBooleanExtra(BoxingViewActivity.EXTRA_TYPE_BACK, false); |
||||||
|
List<BaseMedia> selectedMedias = data.getParcelableArrayListExtra(Boxing.EXTRA_SELECTED_MEDIA); |
||||||
|
onViewActivityRequest(selectedMedias, mMediaAdapter.getAllMedias(), isBackClick); |
||||||
|
if (isBackClick) { |
||||||
|
mMediaAdapter.setSelectedMedias(selectedMedias); |
||||||
|
} |
||||||
|
updateMultiPickerLayoutState(selectedMedias); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private void onViewActivityRequest(List<BaseMedia> selectedMedias, List<BaseMedia> allMedias, boolean isBackClick) { |
||||||
|
if (isBackClick) { |
||||||
|
checkSelectedMedia(allMedias, selectedMedias); |
||||||
|
} else { |
||||||
|
onFinish(selectedMedias); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraActivityResult(int requestCode, int resultCode) { |
||||||
|
showProgressDialog(); |
||||||
|
super.onCameraActivityResult(requestCode, resultCode); |
||||||
|
} |
||||||
|
|
||||||
|
private void showProgressDialog() { |
||||||
|
if (mDialog == null) { |
||||||
|
mDialog = new ProgressDialog(getActivity()); |
||||||
|
mDialog.setIndeterminate(true); |
||||||
|
mDialog.setMessage(getString(R.string.boxing_handling)); |
||||||
|
} |
||||||
|
if (!mDialog.isShowing()) { |
||||||
|
mDialog.show(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void dismissProgressDialog() { |
||||||
|
if (mDialog != null && mDialog.isShowing()) { |
||||||
|
mDialog.hide(); |
||||||
|
mDialog.dismiss(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSaveInstanceState(Bundle outState) { |
||||||
|
super.onSaveInstanceState(outState); |
||||||
|
|
||||||
|
ArrayList<BaseMedia> medias = (ArrayList<BaseMedia>) getMediaAdapter().getSelectedMedias(); |
||||||
|
onSaveMedias(outState, medias); |
||||||
|
} |
||||||
|
|
||||||
|
public void setTitleTxt(TextView titleTxt) { |
||||||
|
mTitleTxt = titleTxt; |
||||||
|
mTitleTxt.setOnClickListener(new View.OnClickListener() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
if (mAlbumPopWindow == null) { |
||||||
|
int height = WindowManagerHelper.getScreenHeight(v.getContext()) - |
||||||
|
(WindowManagerHelper.getToolbarHeight(v.getContext()) |
||||||
|
+ WindowManagerHelper.getStatusBarHeight(v.getContext())); |
||||||
|
View windowView = createWindowView(); |
||||||
|
mAlbumPopWindow = new PopupWindow(windowView, ViewGroup.LayoutParams.MATCH_PARENT, |
||||||
|
height, true); |
||||||
|
mAlbumPopWindow.setAnimationStyle(R.style.Boxing_PopupAnimation); |
||||||
|
mAlbumPopWindow.setOutsideTouchable(true); |
||||||
|
mAlbumPopWindow.setBackgroundDrawable(new ColorDrawable |
||||||
|
(ContextCompat.getColor(v.getContext(), R.color.boxing_colorPrimaryAlpha))); |
||||||
|
mAlbumPopWindow.setContentView(windowView); |
||||||
|
} |
||||||
|
mAlbumPopWindow.showAsDropDown(v, 0, 0); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private View createWindowView() { |
||||||
|
View view = LayoutInflater.from(getActivity()).inflate(R.layout.layout_boxing_album, null); |
||||||
|
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.album_recycleview); |
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext(), LinearLayoutManager.VERTICAL, false)); |
||||||
|
recyclerView.addItemDecoration(new SpacesItemDecoration(2, 1)); |
||||||
|
|
||||||
|
View albumShadowLayout = view.findViewById(R.id.album_shadow); |
||||||
|
albumShadowLayout.setOnClickListener(new View.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
dismissAlbumWindow(); |
||||||
|
} |
||||||
|
}); |
||||||
|
mAlbumWindowAdapter.setAlbumOnClickListener(new OnAlbumItemOnClickListener()); |
||||||
|
recyclerView.setAdapter(mAlbumWindowAdapter); |
||||||
|
return view; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void dismissAlbumWindow() { |
||||||
|
if (mAlbumPopWindow != null && mAlbumPopWindow.isShowing()) { |
||||||
|
mAlbumPopWindow.dismiss(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class ScrollListener extends RecyclerView.OnScrollListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
||||||
|
final int childCount = recyclerView.getChildCount(); |
||||||
|
if (childCount > 0) { |
||||||
|
View lastChild = recyclerView.getChildAt(childCount - 1); |
||||||
|
RecyclerView.Adapter outerAdapter = recyclerView.getAdapter(); |
||||||
|
int lastVisible = recyclerView.getChildAdapterPosition(lastChild); |
||||||
|
if (lastVisible == outerAdapter.getItemCount() - 1 && hasNextPage() && canLoadNextPage()) { |
||||||
|
onLoadNextPage(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class OnMediaClickListener implements View.OnClickListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
BaseMedia media = (BaseMedia) v.getTag(); |
||||||
|
int pos = (int) v.getTag(R.id.media_item_check); |
||||||
|
BoxingConfig.Mode mode = BoxingManager.getInstance().getBoxingConfig().getMode(); |
||||||
|
if (mode == BoxingConfig.Mode.SINGLE_IMG) { |
||||||
|
singleImageClick(media); |
||||||
|
} else if (mode == BoxingConfig.Mode.MULTI_IMG) { |
||||||
|
multiImageClick(pos); |
||||||
|
} else if (mode == BoxingConfig.Mode.VIDEO) { |
||||||
|
videoClick(media); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void videoClick(BaseMedia media) { |
||||||
|
ArrayList<BaseMedia> iMedias = new ArrayList<>(); |
||||||
|
iMedias.add(media); |
||||||
|
onFinish(iMedias); |
||||||
|
} |
||||||
|
|
||||||
|
private void multiImageClick(int pos) { |
||||||
|
if (!mIsPreview) { |
||||||
|
AlbumEntity albumMedia = mAlbumWindowAdapter.getCurrentAlbum(); |
||||||
|
String albumId = albumMedia != null ? albumMedia.mBucketId : AlbumEntity.DEFAULT_NAME; |
||||||
|
mIsPreview = true; |
||||||
|
|
||||||
|
ArrayList<BaseMedia> medias = (ArrayList<BaseMedia>) mMediaAdapter.getSelectedMedias(); |
||||||
|
|
||||||
|
Boxing.get().withIntent(getContext(), BoxingViewActivity.class, medias, pos, albumId) |
||||||
|
.start(BoxingViewFragment.this, BoxingViewFragment.IMAGE_PREVIEW_REQUEST_CODE, BoxingConfig.ViewMode.EDIT); |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void singleImageClick(BaseMedia media) { |
||||||
|
ArrayList<BaseMedia> iMedias = new ArrayList<>(); |
||||||
|
iMedias.add(media); |
||||||
|
if (hasCropBehavior()) { |
||||||
|
startCrop(media, IMAGE_CROP_REQUEST_CODE); |
||||||
|
} else { |
||||||
|
onFinish(iMedias); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private class OnCameraClickListener implements View.OnClickListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
if (!mIsCamera) { |
||||||
|
mIsCamera = true; |
||||||
|
startCamera(getActivity(), BoxingViewFragment.this, BoxingFileHelper.DEFAULT_SUB_DIR); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class OnMediaCheckedListener implements BoxingMediaAdapter.OnMediaCheckedListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onChecked(View view, BaseMedia iMedia) { |
||||||
|
if (!(iMedia instanceof ImageMedia)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
ImageMedia photoMedia = (ImageMedia) iMedia; |
||||||
|
boolean isSelected = !photoMedia.isSelected(); |
||||||
|
MediaItemLayout layout = (MediaItemLayout) view; |
||||||
|
List<BaseMedia> selectedMedias = mMediaAdapter.getSelectedMedias(); |
||||||
|
if (isSelected) { |
||||||
|
if (selectedMedias.size() >= mMaxCount) { |
||||||
|
String warning = getString(R.string.boxing_too_many_picture_fmt, mMaxCount); |
||||||
|
Toast.makeText(getActivity(), warning, Toast.LENGTH_SHORT).show(); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!selectedMedias.contains(photoMedia)) { |
||||||
|
if (photoMedia.isGifOverSize()) { |
||||||
|
Toast.makeText(getActivity(), R.string.boxing_gif_too_big, Toast.LENGTH_SHORT).show(); |
||||||
|
return; |
||||||
|
} |
||||||
|
selectedMedias.add(photoMedia); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (selectedMedias.size() >= 1 && selectedMedias.contains(photoMedia)) { |
||||||
|
selectedMedias.remove(photoMedia); |
||||||
|
} |
||||||
|
} |
||||||
|
photoMedia.setSelected(isSelected); |
||||||
|
layout.setChecked(isSelected); |
||||||
|
updateMultiPickerLayoutState(selectedMedias); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class OnAlbumItemOnClickListener implements BoxingAlbumAdapter.OnAlbumClickListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View view, int pos) { |
||||||
|
BoxingAlbumAdapter adapter = mAlbumWindowAdapter; |
||||||
|
if (adapter != null && adapter.getCurrentAlbumPos() != pos) { |
||||||
|
List<AlbumEntity> albums = adapter.getAlums(); |
||||||
|
adapter.setCurrentAlbumPos(pos); |
||||||
|
|
||||||
|
AlbumEntity albumMedia = albums.get(pos); |
||||||
|
loadMedias(0, albumMedia.mBucketId); |
||||||
|
mTitleTxt.setText(albumMedia.mBucketName == null ? getString(R.string.boxing_default_album_name) : albumMedia.mBucketName); |
||||||
|
|
||||||
|
for (AlbumEntity album : albums) { |
||||||
|
album.mIsSelected = false; |
||||||
|
} |
||||||
|
albumMedia.mIsSelected = true; |
||||||
|
adapter.notifyDataSetChanged(); |
||||||
|
} |
||||||
|
dismissAlbumWindow(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
package com.bilibili.boxing_impl.view; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.util.AttributeSet; |
||||||
|
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager; |
||||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by ChenSL on 2018/3/22. |
||||||
|
*/ |
||||||
|
|
||||||
|
public class HackyGridLayoutManager extends GridLayoutManager { |
||||||
|
public HackyGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
||||||
|
super(context, attrs, defStyleAttr, defStyleRes); |
||||||
|
} |
||||||
|
|
||||||
|
public HackyGridLayoutManager(Context context, int spanCount) { |
||||||
|
super(context, spanCount); |
||||||
|
} |
||||||
|
|
||||||
|
public HackyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { |
||||||
|
super(context, spanCount, orientation, reverseLayout); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { |
||||||
|
try { |
||||||
|
super.onLayoutChildren(recycler, state); |
||||||
|
} catch (IndexOutOfBoundsException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.view; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.view.MotionEvent; |
||||||
|
|
||||||
|
import androidx.viewpager.widget.ViewPager; |
||||||
|
|
||||||
|
/** |
||||||
|
* https://github.com/chrisbanes/PhotoView/issues/35
|
||||||
|
*/ |
||||||
|
public class HackyViewPager extends ViewPager { |
||||||
|
private boolean mIsLocked; |
||||||
|
|
||||||
|
public HackyViewPager(Context context) { |
||||||
|
super(context); |
||||||
|
mIsLocked = false; |
||||||
|
} |
||||||
|
|
||||||
|
public HackyViewPager(Context context, AttributeSet attrs) { |
||||||
|
super(context, attrs); |
||||||
|
mIsLocked = false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) { |
||||||
|
if (!mIsLocked) { |
||||||
|
try { |
||||||
|
return super.onInterceptTouchEvent(ev); |
||||||
|
} catch (IllegalArgumentException e) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onTouchEvent(MotionEvent event) { |
||||||
|
return !mIsLocked && super.onTouchEvent(event); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isLocked() { |
||||||
|
return mIsLocked; |
||||||
|
} |
||||||
|
|
||||||
|
public void setLocked(boolean isLocked) { |
||||||
|
this.mIsLocked = isLocked; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,162 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.view; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.content.res.Configuration; |
||||||
|
import android.text.TextUtils; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.FrameLayout; |
||||||
|
import android.widget.ImageView; |
||||||
|
import android.widget.TextView; |
||||||
|
|
||||||
|
import com.bilibili.boxing.BoxingMediaLoader; |
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.VideoMedia; |
||||||
|
import com.bilibili.boxing_impl.BoxingResHelper; |
||||||
|
import com.bilibili.boxing_impl.WindowManagerHelper; |
||||||
|
import com.ztiany.mediaselector.R; |
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A media layout for {@link androidx.recyclerview.widget.RecyclerView} item, including image and video <br/> |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class MediaItemLayout extends FrameLayout { |
||||||
|
private static final int BIG_IMG_SIZE = 5 * 1024 * 1024; |
||||||
|
|
||||||
|
private ImageView mCheckImg; |
||||||
|
private View mVideoLayout; |
||||||
|
private View mFontLayout; |
||||||
|
private ImageView mCoverImg; |
||||||
|
private ScreenType mScreenType; |
||||||
|
|
||||||
|
private enum ScreenType { |
||||||
|
SMALL(100), NORMAL(180), LARGE(320); |
||||||
|
int value; |
||||||
|
|
||||||
|
ScreenType(int value) { |
||||||
|
this.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
public int getValue() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public MediaItemLayout(Context context) { |
||||||
|
this(context, null, 0); |
||||||
|
} |
||||||
|
|
||||||
|
public MediaItemLayout(Context context, AttributeSet attrs) { |
||||||
|
this(context, attrs, 0); |
||||||
|
} |
||||||
|
|
||||||
|
public MediaItemLayout(Context context, AttributeSet attrs, int defStyleAttr) { |
||||||
|
super(context, attrs, defStyleAttr); |
||||||
|
View view = LayoutInflater.from(context).inflate(R.layout.layout_boxing_media_item, this, true); |
||||||
|
mCoverImg = (ImageView) view.findViewById(R.id.media_item); |
||||||
|
mCheckImg = (ImageView) view.findViewById(R.id.media_item_check); |
||||||
|
mVideoLayout = view.findViewById(R.id.video_layout); |
||||||
|
mFontLayout = view.findViewById(R.id.media_font_layout); |
||||||
|
mScreenType = getScreenType(context); |
||||||
|
setImageRect(context); |
||||||
|
} |
||||||
|
|
||||||
|
private void setImageRect(Context context) { |
||||||
|
int screenHeight = WindowManagerHelper.getScreenHeight(context); |
||||||
|
int screenWidth = WindowManagerHelper.getScreenWidth(context); |
||||||
|
int width = 100; |
||||||
|
if (screenHeight != 0 && screenWidth != 0) { |
||||||
|
width = (screenWidth - getResources().getDimensionPixelOffset(R.dimen.boxing_media_margin) * 4) / 3; |
||||||
|
} |
||||||
|
mCoverImg.getLayoutParams().width = width; |
||||||
|
mCoverImg.getLayoutParams().height = width; |
||||||
|
mFontLayout.getLayoutParams().width = width; |
||||||
|
mFontLayout.getLayoutParams().height = width; |
||||||
|
} |
||||||
|
|
||||||
|
private ScreenType getScreenType(Context context) { |
||||||
|
int type = context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; |
||||||
|
ScreenType result; |
||||||
|
switch (type) { |
||||||
|
case Configuration.SCREENLAYOUT_SIZE_SMALL: |
||||||
|
result = ScreenType.SMALL; |
||||||
|
break; |
||||||
|
case Configuration.SCREENLAYOUT_SIZE_NORMAL: |
||||||
|
result = ScreenType.NORMAL; |
||||||
|
break; |
||||||
|
case Configuration.SCREENLAYOUT_SIZE_LARGE: |
||||||
|
result = ScreenType.LARGE; |
||||||
|
break; |
||||||
|
default: |
||||||
|
result = ScreenType.NORMAL; |
||||||
|
break; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public void setImageRes(@DrawableRes int imageRes) { |
||||||
|
if (mCoverImg != null) { |
||||||
|
mCoverImg.setImageResource(imageRes); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setMedia(BaseMedia media) { |
||||||
|
if (media instanceof ImageMedia) { |
||||||
|
mVideoLayout.setVisibility(GONE); |
||||||
|
setCover(((ImageMedia) media).getThumbnailPath()); |
||||||
|
} else if (media instanceof VideoMedia) { |
||||||
|
mVideoLayout.setVisibility(VISIBLE); |
||||||
|
VideoMedia videoMedia = (VideoMedia) media; |
||||||
|
TextView durationTxt = ((TextView) mVideoLayout.findViewById(R.id.video_duration_txt)); |
||||||
|
durationTxt.setText(videoMedia.getDuration()); |
||||||
|
durationTxt.setCompoundDrawablesWithIntrinsicBounds(BoxingManager.getInstance().getBoxingConfig().getVideoDurationRes(), 0, 0, 0); |
||||||
|
((TextView) mVideoLayout.findViewById(R.id.video_size_txt)).setText(videoMedia.getSizeByUnit()); |
||||||
|
setCover(videoMedia.getPath()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void setCover(@NonNull String path) { |
||||||
|
if (mCoverImg == null || TextUtils.isEmpty(path)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mCoverImg.setTag(R.string.boxing_app_name, path); |
||||||
|
BoxingMediaLoader.getInstance().displayThumbnail(mCoverImg, path, mScreenType.getValue(), mScreenType.getValue()); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") |
||||||
|
public void setChecked(boolean isChecked) { |
||||||
|
if (isChecked) { |
||||||
|
mFontLayout.setVisibility(View.VISIBLE); |
||||||
|
mCheckImg.setImageDrawable(getResources().getDrawable(BoxingResHelper.getMediaCheckedRes())); |
||||||
|
} else { |
||||||
|
mFontLayout.setVisibility(View.GONE); |
||||||
|
mCheckImg.setImageDrawable(getResources().getDrawable(BoxingResHelper.getMediaUncheckedRes())); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,103 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing_impl.view; |
||||||
|
|
||||||
|
import android.graphics.Rect; |
||||||
|
import android.view.View; |
||||||
|
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager; |
||||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class SpacesItemDecoration extends RecyclerView.ItemDecoration { |
||||||
|
private int mSpace; |
||||||
|
private int mSpanCount; |
||||||
|
private int mRadixX; |
||||||
|
private int mItemCountInLastLine; |
||||||
|
private int mOldItemCount = -1; |
||||||
|
|
||||||
|
public SpacesItemDecoration(int space) { |
||||||
|
this(space, 1); |
||||||
|
} |
||||||
|
|
||||||
|
public SpacesItemDecoration(int space, int spanCount) { |
||||||
|
this.mSpace = space; |
||||||
|
this.mSpanCount = spanCount; |
||||||
|
this.mRadixX = space / spanCount; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void getItemOffsets(Rect outRect, View view, final RecyclerView parent, RecyclerView.State state) { |
||||||
|
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); |
||||||
|
final int sumCount = state.getItemCount(); |
||||||
|
final int position = params.getViewLayoutPosition(); |
||||||
|
final int spanSize; |
||||||
|
final int index; |
||||||
|
|
||||||
|
if (params instanceof GridLayoutManager.LayoutParams) { |
||||||
|
GridLayoutManager.LayoutParams gridParams = (GridLayoutManager.LayoutParams) params; |
||||||
|
spanSize = gridParams.getSpanSize(); |
||||||
|
index = gridParams.getSpanIndex(); |
||||||
|
|
||||||
|
if ((position == 0 || mOldItemCount != sumCount) && mSpanCount > 1) { |
||||||
|
int countInLine = 0; |
||||||
|
int spanIndex; |
||||||
|
|
||||||
|
for (int tempPosition = sumCount - mSpanCount; tempPosition < sumCount; tempPosition++) { |
||||||
|
spanIndex = ((GridLayoutManager) parent.getLayoutManager()).getSpanSizeLookup().getSpanIndex(tempPosition, mSpanCount); |
||||||
|
countInLine = spanIndex == 0 ? 1 : countInLine + 1; |
||||||
|
} |
||||||
|
mItemCountInLastLine = countInLine; |
||||||
|
if (mOldItemCount != sumCount) { |
||||||
|
mOldItemCount = sumCount; |
||||||
|
if (position != 0) { |
||||||
|
parent.post(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
parent.invalidateItemDecorations(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (params instanceof StaggeredGridLayoutManager.LayoutParams) { |
||||||
|
spanSize = ((StaggeredGridLayoutManager.LayoutParams) params).isFullSpan() ? mSpanCount : 1; |
||||||
|
index = ((StaggeredGridLayoutManager.LayoutParams) params).getSpanIndex(); |
||||||
|
} else { |
||||||
|
spanSize = 1; |
||||||
|
index = 0; |
||||||
|
} |
||||||
|
|
||||||
|
if (spanSize < 1 || index < 0 || spanSize > mSpanCount) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
outRect.left = mSpace - mRadixX * index; |
||||||
|
outRect.right = mRadixX + mRadixX * (index + spanSize - 1); |
||||||
|
|
||||||
|
if (mSpanCount == 1 && position == sumCount - 1) { |
||||||
|
outRect.bottom = mSpace; |
||||||
|
} else if (position >= sumCount - mItemCountInLastLine && position < sumCount) { |
||||||
|
outRect.bottom = mSpace; |
||||||
|
} |
||||||
|
outRect.top = mSpace; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" > |
||||||
|
|
||||||
|
<alpha |
||||||
|
android:duration="150" |
||||||
|
android:fromAlpha="0.1" |
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator" |
||||||
|
android:toAlpha="1.0" /> |
||||||
|
|
||||||
|
</set> |
@ -0,0 +1,27 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" > |
||||||
|
|
||||||
|
<alpha |
||||||
|
android:duration="150" |
||||||
|
android:fromAlpha="1.0" |
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator" |
||||||
|
android:toAlpha="0.0" /> |
||||||
|
|
||||||
|
</set> |
After Width: | Height: | Size: 422 B |
After Width: | Height: | Size: 162 B |
After Width: | Height: | Size: 351 B |
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 197 B |
After Width: | Height: | Size: 437 B |
After Width: | Height: | Size: 796 B |
After Width: | Height: | Size: 263 B |
After Width: | Height: | Size: 604 B |
@ -0,0 +1,39 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android"> |
||||||
|
|
||||||
|
<item android:state_enabled="true" android:state_pressed="true"> |
||||||
|
<shape> |
||||||
|
<corners android:radius="@dimen/boxing_corner_radius" /> |
||||||
|
<solid android:color="@color/boxing_black" /> |
||||||
|
</shape> |
||||||
|
</item> |
||||||
|
|
||||||
|
<item android:state_enabled="true"> |
||||||
|
<shape> |
||||||
|
<solid android:color="@color/boxing_black" /> |
||||||
|
<corners android:radius="@dimen/boxing_corner_radius" /> |
||||||
|
</shape> |
||||||
|
</item> |
||||||
|
<item android:state_enabled="false"> |
||||||
|
<shape> |
||||||
|
<solid android:color="@color/boxing_gray" /> |
||||||
|
<corners android:radius="@dimen/boxing_corner_radius" /> |
||||||
|
</shape> |
||||||
|
</item> |
||||||
|
</selector> |
@ -0,0 +1,27 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:shape="rectangle"> |
||||||
|
|
||||||
|
<corners |
||||||
|
android:radius="@dimen/boxing_corner_radius" /> |
||||||
|
<solid |
||||||
|
android:color="#fafafa" /> |
||||||
|
|
||||||
|
</shape> |
@ -0,0 +1,33 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:shape="oval"> |
||||||
|
|
||||||
|
<solid android:color="@color/boxing_colorPrimaryAlpha"/> |
||||||
|
|
||||||
|
<stroke |
||||||
|
android:width="0.3dp" |
||||||
|
android:color="@color/boxing_white"/> |
||||||
|
|
||||||
|
<size |
||||||
|
android:width="18dp" |
||||||
|
android:height="18dp"/> |
||||||
|
|
||||||
|
|
||||||
|
</shape> |
@ -0,0 +1,50 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar |
||||||
|
android:id="@+id/nav_top_bar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="?android:attr/actionBarSize" |
||||||
|
android:elevation="2dp" |
||||||
|
app:theme="@style/Boxing.ToolbarTheme"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/pick_album_txt" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_centerInParent="true" |
||||||
|
android:drawablePadding="8dp" |
||||||
|
android:drawableRight="@drawable/abc_spinner_mtrl_am_alpha" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:paddingLeft="8dp" |
||||||
|
android:paddingRight="8dp" |
||||||
|
android:text="@string/boxing_default_album"/> |
||||||
|
</androidx.appcompat.widget.Toolbar> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:id="@+id/content_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:layout_weight="1"/> |
||||||
|
</LinearLayout> |
@ -0,0 +1,50 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<include layout="@layout/layout_boxing_app_bar"/> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/media_result" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:padding="@dimen/boxing_activity_horizontal_margin"/> |
||||||
|
|
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:id="@+id/content_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:background="@color/boxing_white1" |
||||||
|
app:behavior_hideable="true" |
||||||
|
app:behavior_peekHeight="300dp" |
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"/> |
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
@ -0,0 +1,83 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:id="@+id/image_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:background="@color/boxing_black1" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar |
||||||
|
android:id="@+id/nav_top_bar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:elevation="2dp" |
||||||
|
android:layout_height="?android:attr/actionBarSize" |
||||||
|
app:theme="@style/Boxing.ToolbarTheme"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/title" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_centerInParent="true" |
||||||
|
android:drawablePadding="8dp" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:paddingLeft="8dp" |
||||||
|
android:paddingRight="8dp" |
||||||
|
android:textAppearance="@style/Boxing.TextAppearance.App.Medium"/> |
||||||
|
</androidx.appcompat.widget.Toolbar> |
||||||
|
|
||||||
|
<ProgressBar |
||||||
|
android:id="@+id/loading" |
||||||
|
style="?android:attr/progressBarStyleLarge" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:layout_weight="1" |
||||||
|
android:indeterminate="true"/> |
||||||
|
|
||||||
|
<com.bilibili.boxing_impl.view.HackyViewPager |
||||||
|
android:id="@+id/pager" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:layout_weight="1" |
||||||
|
android:visibility="gone"/> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:id="@+id/item_choose_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="56dp" |
||||||
|
android:layout_gravity="bottom" |
||||||
|
android:elevation="2dp"> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/image_items_ok" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="30dp" |
||||||
|
android:layout_gravity="right|center_vertical" |
||||||
|
android:layout_marginRight="@dimen/boxing_item_spacing" |
||||||
|
android:minWidth="60dp" |
||||||
|
android:paddingLeft="@dimen/boxing_item_half_spacing" |
||||||
|
android:paddingRight="@dimen/boxing_item_half_spacing" |
||||||
|
android:text="@string/boxing_ok" |
||||||
|
android:background="@drawable/selector_boxing_btn_solid" |
||||||
|
android:textColor="@color/boxing_white"/> |
||||||
|
</FrameLayout> |
||||||
|
</LinearLayout> |
@ -0,0 +1,78 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:id="@+id/container" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView |
||||||
|
android:id="@+id/media_recycleview" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:layout_weight="1" |
||||||
|
android:visibility="gone" |
||||||
|
android:scrollbars="vertical"/> |
||||||
|
|
||||||
|
<include |
||||||
|
android:id="@+id/empty_txt" |
||||||
|
layout="@layout/layout_boxing_empty_txt"/> |
||||||
|
|
||||||
|
<ProgressBar |
||||||
|
android:id="@+id/loading" |
||||||
|
style="?android:attr/progressBarStyleLarge" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:indeterminate="true"/> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:id="@+id/multi_picker_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="56dp" |
||||||
|
android:background="@color/boxing_white"> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/choose_preview_btn" |
||||||
|
android:layout_width="60dp" |
||||||
|
android:layout_height="30dp" |
||||||
|
android:layout_gravity="left|center_vertical" |
||||||
|
android:layout_marginLeft="@dimen/boxing_item_spacing" |
||||||
|
android:background="@drawable/selector_boxing_btn_solid" |
||||||
|
android:text="@string/boxing_preview" |
||||||
|
android:textAppearance="@style/Boxing.TextAppearance.App.Medium" |
||||||
|
android:textColor="@color/boxing_white"/> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/choose_ok_btn" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="30dp" |
||||||
|
android:layout_gravity="right|center_vertical" |
||||||
|
android:layout_marginRight="@dimen/boxing_item_spacing" |
||||||
|
android:background="@drawable/selector_boxing_btn_solid" |
||||||
|
android:minWidth="60dp" |
||||||
|
android:paddingLeft="@dimen/boxing_item_half_spacing" |
||||||
|
android:paddingRight="@dimen/boxing_item_half_spacing" |
||||||
|
android:text="@string/boxing_ok" |
||||||
|
android:textAppearance="@style/Boxing.TextAppearance.App.Medium" |
||||||
|
android:textColor="@color/boxing_white"/> |
||||||
|
</FrameLayout> |
||||||
|
|
||||||
|
</LinearLayout> |
@ -0,0 +1,72 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:orientation="horizontal" |
||||||
|
android:background="@color/boxing_black" |
||||||
|
android:padding="@dimen/boxing_activity_vertical_margin"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/textView" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_weight="1" |
||||||
|
android:gravity="left" |
||||||
|
android:text="@string/boxing_default_album" |
||||||
|
android:textColor="@color/boxing_white"/> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/finish_txt" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_weight="1" |
||||||
|
android:gravity="right" |
||||||
|
android:text="@string/boxing_finish" |
||||||
|
android:textColor="@color/boxing_white"/> |
||||||
|
|
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView |
||||||
|
android:id="@+id/media_recycleview" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:background="@color/boxing_white1" |
||||||
|
android:visibility="gone"/> |
||||||
|
|
||||||
|
<ProgressBar |
||||||
|
android:id="@+id/loading" |
||||||
|
style="?android:attr/progressBarStyleLarge" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_weight="1" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:indeterminate="true"/> |
||||||
|
|
||||||
|
<include |
||||||
|
android:id="@+id/empty_txt" |
||||||
|
layout="@layout/layout_boxing_empty_txt"/> |
||||||
|
|
||||||
|
</LinearLayout> |
@ -0,0 +1,37 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<uk.co.senab.photoview.PhotoView |
||||||
|
android:id="@+id/photo_view" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:layout_margin="@dimen/boxing_item_half_spacing"/> |
||||||
|
|
||||||
|
<ProgressBar |
||||||
|
android:id="@+id/loading" |
||||||
|
style="?android:attr/progressBarStyleLarge" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:indeterminate="true"/> |
||||||
|
</FrameLayout> |
@ -0,0 +1,38 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView |
||||||
|
android:id="@+id/album_recycleview" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="300dp" |
||||||
|
android:background="@color/boxing_white" |
||||||
|
android:scrollbars="vertical"/> |
||||||
|
|
||||||
|
<View |
||||||
|
android:id="@+id/album_shadow" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:background="@color/boxing_colorPrimaryAlpha"/> |
||||||
|
|
||||||
|
</LinearLayout> |
@ -0,0 +1,71 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:id="@+id/album_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="56dp" |
||||||
|
android:background="?android:selectableItemBackground" |
||||||
|
android:paddingTop="@dimen/boxing_item_spacing"> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/album_thumbnail" |
||||||
|
android:layout_width="50dp" |
||||||
|
android:layout_height="50dp" |
||||||
|
android:layout_alignParentLeft="true" |
||||||
|
android:layout_centerVertical="true" |
||||||
|
android:layout_marginLeft="12dp" |
||||||
|
android:scaleType="centerCrop"/> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/album_name" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_centerVertical="true" |
||||||
|
android:layout_marginLeft="12dp" |
||||||
|
android:layout_toRightOf="@id/album_thumbnail" |
||||||
|
android:ellipsize="end" |
||||||
|
android:maxEms="12" |
||||||
|
android:singleLine="true" |
||||||
|
android:textAppearance="@style/Boxing.TextAppearance.App.Medium" |
||||||
|
android:textColor="@color/boxing_gray1" |
||||||
|
tools:text="My Favorite Album"/> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/album_size" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_centerVertical="true" |
||||||
|
android:layout_marginLeft="4dp" |
||||||
|
android:layout_toRightOf="@id/album_name" |
||||||
|
android:textAppearance="@style/Boxing.TextAppearance.App.Medium" |
||||||
|
android:textColor="@color/boxing_gray1" |
||||||
|
tools:text="(980)"/> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/album_checked" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_alignParentRight="true" |
||||||
|
android:layout_centerVertical="true" |
||||||
|
android:layout_marginRight="8dp" |
||||||
|
android:src="@drawable/ic_boxing_check_black" |
||||||
|
android:visibility="gone" |
||||||
|
tools:visibility="visible"/> |
||||||
|
</RelativeLayout> |
@ -0,0 +1,26 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:id="@+id/nav_top_bar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="56dp" |
||||||
|
app:theme="@style/Boxing.ToolbarTheme"/> |
||||||
|
|
@ -0,0 +1,32 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<TextView |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:id="@+id/empty_txt" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:layout_weight="1" |
||||||
|
android:drawablePadding="4dp" |
||||||
|
android:gravity="center" |
||||||
|
android:text="@string/boxing_nothing_found" |
||||||
|
android:textColor="@color/boxing_black" |
||||||
|
android:visibility="gone" |
||||||
|
tools:visibility="visible"/> |
@ -0,0 +1,83 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/media_item" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:scaleType="centerCrop"/> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/media_item_check" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="right" |
||||||
|
android:paddingTop="6dp" |
||||||
|
android:paddingRight="6dp" |
||||||
|
android:paddingBottom="12dp" |
||||||
|
android:paddingLeft="12dp" |
||||||
|
android:src="@drawable/shape_boxing_unchecked"/> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:id="@+id/video_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="bottom" |
||||||
|
android:background="@color/boxing_colorPrimaryAlpha" |
||||||
|
android:paddingBottom="2dp" |
||||||
|
android:paddingTop="4dp" |
||||||
|
android:visibility="gone" |
||||||
|
tools:visibility="visible"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/video_duration_txt" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="center_vertical|left" |
||||||
|
android:drawablePadding="4dp" |
||||||
|
android:maxEms="6" |
||||||
|
android:paddingLeft="4dp" |
||||||
|
android:textAppearance="@style/Boxing.TextAppearance.App.Medium" |
||||||
|
android:textColor="@color/boxing_white" |
||||||
|
tools:text="00:30"/> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/video_size_txt" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="center_vertical|right" |
||||||
|
android:maxEms="4" |
||||||
|
android:paddingRight="4dp" |
||||||
|
android:textAppearance="@style/Boxing.TextAppearance.App.Medium" |
||||||
|
android:textColor="@color/boxing_white" |
||||||
|
tools:text="20M"/> |
||||||
|
</FrameLayout> |
||||||
|
|
||||||
|
<View |
||||||
|
android:id="@+id/media_font_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:visibility="gone" |
||||||
|
android:background="@color/boxing_black_alpha15"/> |
||||||
|
</merge> |
@ -0,0 +1,35 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:id="@+id/camera_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:background="@color/boxing_black" |
||||||
|
android:foreground="?android:selectableItemBackground" |
||||||
|
android:minHeight="100dp" |
||||||
|
android:minWidth="100dp"> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/camera_img" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_centerInParent="true" |
||||||
|
android:textColor="@color/boxing_white" |
||||||
|
android:textSize="14sp"/> |
||||||
|
</RelativeLayout> |
@ -0,0 +1,23 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<com.bilibili.boxing_impl.view.MediaItemLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:id="@+id/media_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" /> |
@ -0,0 +1,25 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:id="@+id/media_item" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="center" |
||||||
|
android:scaleType="centerCrop"/> |
@ -0,0 +1,27 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"> |
||||||
|
|
||||||
|
<item |
||||||
|
android:id="@+id/menu_image_item_selected" |
||||||
|
android:icon="@drawable/shape_boxing_unchecked" |
||||||
|
android:title="" |
||||||
|
app:showAsAction="always" /> |
||||||
|
</menu> |
@ -0,0 +1,48 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<resources> |
||||||
|
<string name="boxing_ok">OK</string> |
||||||
|
<string name="boxing_fab_label_camera">take a shot</string> |
||||||
|
|
||||||
|
<string name="boxing_image_preview_title_fmt">%1$s/%2$s</string> |
||||||
|
<string name="boxing_image_preview_ok_fmt">%1$s/%2$s OK</string> |
||||||
|
<string name="boxing_image_select_ok_fmt">(%1$s/%2$s) OK</string> |
||||||
|
|
||||||
|
<string name="boxing_max_image_over_fmt">%d picture is limited</string> |
||||||
|
<string name="boxing_gif_too_big">the gif is too big!</string> |
||||||
|
<string name="boxing_pick_single_video">Pick a video</string> |
||||||
|
<string name="boxing_pick_multi_image">Pick pictures</string> |
||||||
|
<string name="boxing_pick_single_image_crop">Pick a picture with crop</string> |
||||||
|
<string name="boxing_pick_single_image">Pick a picture</string> |
||||||
|
<string name="boxing_video_title">videos</string> |
||||||
|
<string name="boxing_storage_deny">Device storage read error or temporarily unavailable. Please try again later</string> |
||||||
|
<string name="boxing_finish">finish</string> |
||||||
|
<string name="boxing_start_pick">start</string> |
||||||
|
<string name="boxing_load_image_fail">load fail!</string> |
||||||
|
<string name="boxing_storage_permission_deny">You need to access the storage device to select the picture |
||||||
|
, please allow "storage space" right in the "System Settings" dialog box or authorization.</string> |
||||||
|
<string name="boxing_camera_permission_deny">To access your camera to take pictures, |
||||||
|
enable the "Use Camera" permission in the "System Settings" or Authorization dialog box.</string> |
||||||
|
<string name="boxing_default_album">Pictures</string> |
||||||
|
<string name="boxing_crop_error">sorry, error occurs when cropping</string> |
||||||
|
<string name="boxing_nothing_found">nothing!</string> |
||||||
|
<string name="boxing_too_many_picture_fmt">You can only select up to %d pictures</string> |
||||||
|
<string name="boxing_default_album_name">Pictures</string> |
||||||
|
<string name="boxing_preview">Preview</string> |
||||||
|
</resources> |
@ -0,0 +1,37 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<resources> |
||||||
|
<color name="boxing_colorPrimary">#000000</color> |
||||||
|
<color name="boxing_colorPrimaryDark">#ff212121</color> |
||||||
|
<color name="boxing_colorPrimaryAlpha">#44000000</color> |
||||||
|
<color name="boxing_colorAccent">#FF4081</color> |
||||||
|
|
||||||
|
<color name="boxing_gray">#999999</color> |
||||||
|
<color name="boxing_gray1">#333333</color> |
||||||
|
|
||||||
|
<color name="boxing_white">#ffffff</color> |
||||||
|
<color name="boxing_white1">#fafafa</color> |
||||||
|
<color name="boxing_white2">#eaeaea</color> |
||||||
|
|
||||||
|
<color name="boxing_black">#000000</color> |
||||||
|
<color name="boxing_black1">#1b1b1b</color> |
||||||
|
<color name="boxing_black_alpha15">#26000000</color> |
||||||
|
<color name="boxing_black_alpha20">#33000000</color> |
||||||
|
<color name="boxing_black_alpha25">#3c000000</color> |
||||||
|
</resources> |
@ -0,0 +1,32 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<resources> |
||||||
|
<dimen name="boxing_activity_horizontal_margin">16dp</dimen> |
||||||
|
<dimen name="boxing_activity_vertical_margin">16dp</dimen> |
||||||
|
|
||||||
|
<dimen name="boxing_text_size_xlarge">16sp</dimen> |
||||||
|
<dimen name="boxing_text_size_large">14sp</dimen> |
||||||
|
<dimen name="boxing_text_size_medium">12sp</dimen> |
||||||
|
<dimen name="boxing_text_size_small">10sp</dimen> |
||||||
|
|
||||||
|
<dimen name="boxing_item_spacing">8dp</dimen> |
||||||
|
<dimen name="boxing_item_half_spacing">4dp</dimen> |
||||||
|
<dimen name="boxing_media_margin">2dp</dimen> |
||||||
|
<dimen name="boxing_corner_radius">2dp</dimen> |
||||||
|
</resources> |
@ -0,0 +1,52 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<resources> |
||||||
|
<string name="boxing_app_name">Boxing</string> |
||||||
|
<string name="boxing_ok">确定</string> |
||||||
|
<string name="boxing_fab_label_camera">拍照</string> |
||||||
|
|
||||||
|
<string name="boxing_image_preview_title_fmt">%1$s/%2$s</string> |
||||||
|
<string name="boxing_image_preview_ok_fmt">%1$s/%2$s 确定</string> |
||||||
|
<string name="boxing_image_select_ok_fmt">(%1$s/%2$s) 确定</string> |
||||||
|
<string name="boxing_album_images_fmt">(%d)</string> |
||||||
|
|
||||||
|
<string name="boxing_max_image_over_fmt">你最多只能选择%d张图片</string> |
||||||
|
<string name="boxing_gif_too_big">您选择的gif图片过大,请压缩后再上传</string> |
||||||
|
<string name="boxing_pick_single_video">视频单选</string> |
||||||
|
<string name="boxing_pick_multi_image">图片多选</string> |
||||||
|
<string name="boxing_pick_single_image_crop">图片单选加裁剪</string> |
||||||
|
<string name="boxing_pick_single_image">图片单选</string> |
||||||
|
<string name="boxing_video_title">视频相册</string> |
||||||
|
<string name="boxing_finish">完成</string> |
||||||
|
<string name="boxing_start_pick">开始选图</string> |
||||||
|
<string name="boxing_load_image_fail">肥肠抱歉,加载出错啦</string> |
||||||
|
<string name="boxing_storage_permission_deny">需要访问你的存储设备来选择图片,请在“系统设置”或授权对话框中允许“存储空间”权限。</string> |
||||||
|
<string name="boxing_camera_permission_deny">需要访问你的相机来拍照,请在“系统设置”或授权对话框中允许“使用相机”权限。</string> |
||||||
|
<string name="boxing_storage_deny">设备存储读取出错或暂不可用,请稍候重试</string> |
||||||
|
<string name="boxing_crop_error">肥肠抱歉,裁剪出错啦</string> |
||||||
|
<string name="boxing_camera_unavailble">相机不可用,请检查相机权限!</string> |
||||||
|
<string name="boxing_handling">处理中</string> |
||||||
|
<string name="boxing_default_album">默认相册</string> |
||||||
|
<string name="boxing_nothing_found">什么也找不到</string> |
||||||
|
<string name="boxing_too_many_picture_fmt">您最多只能选择%d张图片</string> |
||||||
|
<string name="boxing_default_album_name">所有相片</string> |
||||||
|
<string name="boxing_preview">预览</string> |
||||||
|
|
||||||
|
|
||||||
|
</resources> |
@ -0,0 +1,50 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<resources> |
||||||
|
|
||||||
|
<style name="Boxing.AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> |
||||||
|
<item name="colorPrimary">@color/boxing_colorPrimary</item> |
||||||
|
<item name="colorPrimaryDark">@color/boxing_colorPrimaryDark</item> |
||||||
|
<item name="colorAccent">@color/boxing_colorAccent</item> |
||||||
|
</style> |
||||||
|
|
||||||
|
<!-- BoxingActivity Theme--> |
||||||
|
<style name="Boxing.AppTheme.NoActionBar" parent="Boxing.AppTheme"> |
||||||
|
<item name="android:windowNoTitle">true</item> |
||||||
|
<item name="android:fitsSystemWindows">false</item> |
||||||
|
</style> |
||||||
|
|
||||||
|
<style name="Boxing.PopupAnimation" parent="android:Animation"> |
||||||
|
<item name="android:windowEnterAnimation">@anim/boxing_fade_in</item> |
||||||
|
<item name="android:windowExitAnimation">@anim/boxing_fade_out</item> |
||||||
|
</style> |
||||||
|
|
||||||
|
<style name="Boxing.ToolbarTheme" parent="Boxing.AppTheme.NoActionBar" > |
||||||
|
<item name="titleTextColor">@color/boxing_white</item> |
||||||
|
<item name="android:textColorSecondary">@color/boxing_white</item> |
||||||
|
<item name="android:textColor">@color/boxing_white</item> |
||||||
|
<item name="android:background">@color/boxing_colorPrimaryDark</item> |
||||||
|
</style> |
||||||
|
|
||||||
|
<!-- TextAppearance --> |
||||||
|
<style name="Boxing.TextAppearance.App.Medium" parent="TextAppearance.AppCompat"> |
||||||
|
<item name="android:textSize">@dimen/boxing_text_size_medium</item> |
||||||
|
</style> |
||||||
|
|
||||||
|
</resources> |
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing; |
||||||
|
|
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Bundle; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.presenter.PickerContract; |
||||||
|
import com.bilibili.boxing.presenter.PickerPresenter; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.appcompat.app.AppCompatActivity; |
||||||
|
|
||||||
|
/** |
||||||
|
* A abstract class to connect {@link com.bilibili.boxing.presenter.PickerContract.View} and {@link com.bilibili.boxing.presenter.PickerContract.Presenter}. |
||||||
|
* one job has to be done. override {@link #onCreateBoxingView(ArrayList)} to create a subclass for {@link AbsBoxingViewFragment}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public abstract class AbsBoxingActivity extends AppCompatActivity implements Boxing.OnBoxingFinishListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
AbsBoxingViewFragment view = onCreateBoxingView(getSelectedMedias(getIntent())); |
||||||
|
BoxingConfig pickerConfig = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
view.setPresenter(new PickerPresenter(view)); |
||||||
|
view.setPickerConfig(pickerConfig); |
||||||
|
Boxing.get().setupFragment(view, this); |
||||||
|
} |
||||||
|
|
||||||
|
private ArrayList<BaseMedia> getSelectedMedias(Intent intent) { |
||||||
|
return intent.getParcelableArrayListExtra(Boxing.EXTRA_SELECTED_MEDIA); |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingConfig getBoxingConfig() { |
||||||
|
return BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* create a {@link PickerContract.View} attaching to |
||||||
|
* {@link PickerContract.Presenter},call in {@link #onCreate(Bundle)} |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
public abstract AbsBoxingViewFragment onCreateBoxingView(ArrayList<BaseMedia> medias); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,241 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.os.PersistableBundle; |
||||||
|
import android.widget.ImageView; |
||||||
|
|
||||||
|
import com.bilibili.boxing.loader.IBoxingCallback; |
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.presenter.PickerContract; |
||||||
|
import com.bilibili.boxing.presenter.PickerPresenter; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.appcompat.app.AppCompatActivity; |
||||||
|
|
||||||
|
/** |
||||||
|
* A abstract class which implements {@link PickerContract.View} for custom media view. |
||||||
|
* For view big images. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public abstract class AbsBoxingViewActivity extends AppCompatActivity implements PickerContract.View { |
||||||
|
ArrayList<BaseMedia> mSelectedImages; |
||||||
|
String mAlbumId; |
||||||
|
int mStartPos; |
||||||
|
|
||||||
|
private PickerContract.Presenter mPresenter; |
||||||
|
|
||||||
|
/** |
||||||
|
* start loading when the permission request is completed. |
||||||
|
* call {@link #loadMedias()} or {@link #loadMedias(int, String)}. |
||||||
|
*/ |
||||||
|
public abstract void startLoading(); |
||||||
|
|
||||||
|
/** |
||||||
|
* override this method to handle the medias. |
||||||
|
* make sure {@link #loadMedias()} ()} being called first. |
||||||
|
* |
||||||
|
* @param medias the results of medias |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void showMedia(@Nullable List<BaseMedia> medias, int allCount) { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void showAlbum(@Nullable List<AlbumEntity> albums) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* to clear all medias the first time(the page number is 0). do some clean work. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void clearMedia() { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
BoxingConfig config; |
||||||
|
if (savedInstanceState != null) { |
||||||
|
config = savedInstanceState.getParcelable(Boxing.EXTRA_CONFIG); |
||||||
|
} else { |
||||||
|
config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
} |
||||||
|
setPickerConfig(config); |
||||||
|
parseSelectedMedias(savedInstanceState, getIntent()); |
||||||
|
setPresenter(new PickerPresenter(this)); |
||||||
|
} |
||||||
|
|
||||||
|
private void parseSelectedMedias(Bundle savedInstanceState, Intent intent) { |
||||||
|
if (savedInstanceState != null) { |
||||||
|
mSelectedImages = savedInstanceState.getParcelableArrayList(Boxing.EXTRA_SELECTED_MEDIA); |
||||||
|
mAlbumId = savedInstanceState.getString(Boxing.EXTRA_ALBUM_ID); |
||||||
|
mStartPos = savedInstanceState.getInt(Boxing.EXTRA_START_POS, 0); |
||||||
|
} else if (intent != null) { |
||||||
|
mStartPos = intent.getIntExtra(Boxing.EXTRA_START_POS, 0); |
||||||
|
mSelectedImages = intent.getParcelableArrayListExtra(Boxing.EXTRA_SELECTED_MEDIA); |
||||||
|
mAlbumId = intent.getStringExtra(Boxing.EXTRA_ALBUM_ID); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public final void setPresenter(@NonNull PickerContract.Presenter presenter) { |
||||||
|
this.mPresenter = presenter; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the {@link ContentResolver} |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public final ContentResolver getAppCr() { |
||||||
|
return getApplicationContext().getContentResolver(); |
||||||
|
} |
||||||
|
|
||||||
|
public final void loadRawImage(@NonNull ImageView img, @NonNull String path, int width, int height, IBoxingCallback callback) { |
||||||
|
BoxingMediaLoader.getInstance().displayRaw(img, path, width, height, callback); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* called the job is done.Click the ok button, take a photo from camera, crop a photo. |
||||||
|
* most of the time, you do not have to override. |
||||||
|
* |
||||||
|
* @param medias the list of selection |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onFinish(@NonNull List<BaseMedia> medias) { |
||||||
|
Intent intent = new Intent(); |
||||||
|
intent.putParcelableArrayListExtra(Boxing.EXTRA_RESULT, (ArrayList<BaseMedia>) medias); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* need crop or not |
||||||
|
* |
||||||
|
* @return true, need it. |
||||||
|
*/ |
||||||
|
public final boolean hasCropBehavior() { |
||||||
|
BoxingConfig config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
return config != null && config.isSingleImageMode() && config.getCropOption() != null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* to start the crop behavior, call it when {@link #hasCropBehavior()} return true. |
||||||
|
* |
||||||
|
* @param media the media to be cropped. |
||||||
|
* @param requestCode The integer request code originally supplied to |
||||||
|
* startActivityForResult(), allowing you to identify who this |
||||||
|
* result came from. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public final void startCrop(@NonNull BaseMedia media, int requestCode) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set or update the config.most of the time, you do not have to call it. |
||||||
|
* |
||||||
|
* @param config {@link BoxingConfig} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public final void setPickerConfig(BoxingConfig config) { |
||||||
|
if (config == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
BoxingManager.getInstance().setBoxingConfig(config); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) { |
||||||
|
super.onSaveInstanceState(outState, outPersistentState); |
||||||
|
outState.putParcelable(Boxing.EXTRA_CONFIG, BoxingManager.getInstance().getBoxingConfig()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* call this to clear resource. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onDestroy() { |
||||||
|
super.onDestroy(); |
||||||
|
if (mPresenter != null) { |
||||||
|
mPresenter.destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* in {@link BoxingConfig.Mode#MULTI_IMG}, call this to pick the selected medias in all medias. |
||||||
|
*/ |
||||||
|
public final void checkSelectedMedia(List<BaseMedia> allMedias, List<BaseMedia> selectedMedias) { |
||||||
|
mPresenter.checkSelectedMedia(allMedias, selectedMedias); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* load first page of medias. |
||||||
|
* use {@link #showMedia(List, int)} to get the result. |
||||||
|
*/ |
||||||
|
public final void loadMedias() { |
||||||
|
mPresenter.loadMedias(0, AlbumEntity.DEFAULT_NAME); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* load the medias for the specify page and album id. |
||||||
|
* use {@link #showMedia(List, int)} to get the result. |
||||||
|
* |
||||||
|
* @param page page numbers. |
||||||
|
* @param albumId the album id is {@link AlbumEntity#mBucketId}. |
||||||
|
*/ |
||||||
|
public final void loadMedias(int page, String albumId) { |
||||||
|
mPresenter.loadMedias(page, albumId); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the max count set before |
||||||
|
*/ |
||||||
|
public final int getMaxCount() { |
||||||
|
BoxingConfig config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
if (config == null) { |
||||||
|
return BoxingConfig.DEFAULT_SELECTED_COUNT; |
||||||
|
} |
||||||
|
return config.getMaxCount(); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
public final ArrayList<BaseMedia> getSelectedImages() { |
||||||
|
if (mSelectedImages != null) { |
||||||
|
return mSelectedImages; |
||||||
|
} |
||||||
|
return new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
public final String getAlbumId() { |
||||||
|
return mAlbumId; |
||||||
|
} |
||||||
|
|
||||||
|
public final int getStartPos() { |
||||||
|
return mStartPos; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,485 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing; |
||||||
|
|
||||||
|
import android.Manifest; |
||||||
|
import android.app.Activity; |
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.content.Intent; |
||||||
|
import android.content.pm.PackageManager; |
||||||
|
import android.net.Uri; |
||||||
|
import android.os.Build; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.view.View; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingBuilderConfig; |
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.config.BoxingCropOption; |
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.presenter.PickerContract; |
||||||
|
import com.bilibili.boxing.utils.CameraPickerHelper; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.lang.ref.WeakReference; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.core.content.ContextCompat; |
||||||
|
import androidx.fragment.app.Fragment; |
||||||
|
|
||||||
|
import static androidx.core.content.PermissionChecker.PERMISSION_GRANTED; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A abstract class which implements {@link PickerContract.View} for custom media view. |
||||||
|
* only one methods need to override {@link #startLoading()}, but there is more function to achieve by |
||||||
|
* checking every method can override. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public abstract class AbsBoxingViewFragment extends Fragment implements PickerContract.View { |
||||||
|
public static final String[] STORAGE_PERMISSIONS = |
||||||
|
{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}; |
||||||
|
public static final String[] CAMERA_PERMISSIONS = {Manifest.permission.CAMERA}; |
||||||
|
|
||||||
|
private static final int REQUEST_CODE_PERMISSION = 233; |
||||||
|
|
||||||
|
private PickerContract.Presenter mPresenter; |
||||||
|
private CameraPickerHelper mCameraPicker; |
||||||
|
private Boxing.OnBoxingFinishListener mOnFinishListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* start loading when the permission request is completed. |
||||||
|
* call {@link #loadMedias()} or {@link #loadMedias(int, String)}, call {@link #loadAlbum()} if albums needed. |
||||||
|
*/ |
||||||
|
public abstract void startLoading(); |
||||||
|
|
||||||
|
/** |
||||||
|
* called when request {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} and {@link Manifest.permission#CAMERA} permission error. |
||||||
|
* |
||||||
|
* @param e a IllegalArgumentException, IllegalStateException or SecurityException will be throw |
||||||
|
*/ |
||||||
|
public void onRequestPermissionError(String[] permissions, Exception e) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* called when request {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} and {@link Manifest.permission#CAMERA} permission successfully. |
||||||
|
*/ |
||||||
|
public void onRequestPermissionSuc(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the result of using camera to take a photo. |
||||||
|
* |
||||||
|
* @param media {@link BaseMedia} |
||||||
|
*/ |
||||||
|
public void onCameraFinish(BaseMedia media) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* called when camera start error |
||||||
|
*/ |
||||||
|
public void onCameraError() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* must override when care about the input medias, which means you call {@link #setSelectedBundle(ArrayList)} first. |
||||||
|
* this method is called in {@link Fragment#onCreate(Bundle)}, so override this rather than {@link Fragment#onCreate(Bundle)}. |
||||||
|
* |
||||||
|
* @param bundle If the fragment is being re-created from |
||||||
|
* a previous saved state, this is the state. |
||||||
|
* @param selectedMedias the input medias, the parameter of {@link #setSelectedBundle(ArrayList)}. |
||||||
|
*/ |
||||||
|
public void onCreateWithSelectedMedias(Bundle bundle, @Nullable List<BaseMedia> selectedMedias) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* override this method to handle the medias. |
||||||
|
* make sure {@link #loadMedias()} ()} being called first. |
||||||
|
* |
||||||
|
* @param medias the results of medias |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void showMedia(@Nullable List<BaseMedia> medias, int allCount) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* override this method to handle the album. |
||||||
|
* make sure {@link #loadAlbum()} being called first. |
||||||
|
* |
||||||
|
* @param albums the results of albums |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void showAlbum(@Nullable List<AlbumEntity> albums) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* to clear all medias the first time(the page number is 0). do some clean work. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void clearMedia() { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||||
|
BoxingConfig config; |
||||||
|
if (savedInstanceState != null) { |
||||||
|
config = savedInstanceState.getParcelable(Boxing.EXTRA_CONFIG); |
||||||
|
} else { |
||||||
|
config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
} |
||||||
|
setPickerConfig(config); |
||||||
|
onCreateWithSelectedMedias(savedInstanceState, parseSelectedMedias(savedInstanceState, getArguments())); |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
|
||||||
|
initCameraPhotoPicker(savedInstanceState); |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private ArrayList<BaseMedia> parseSelectedMedias(Bundle savedInstanceState, Bundle argument) { |
||||||
|
ArrayList<BaseMedia> selectedMedias = null; |
||||||
|
if (savedInstanceState != null) { |
||||||
|
selectedMedias = savedInstanceState.getParcelableArrayList(Boxing.EXTRA_SELECTED_MEDIA); |
||||||
|
} else if (argument != null) { |
||||||
|
selectedMedias = argument.getParcelableArrayList(Boxing.EXTRA_SELECTED_MEDIA); |
||||||
|
} |
||||||
|
return selectedMedias; |
||||||
|
} |
||||||
|
|
||||||
|
private void initCameraPhotoPicker(Bundle savedInstanceState) { |
||||||
|
BoxingConfig config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
if (config == null || !config.isNeedCamera()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mCameraPicker = new CameraPickerHelper(savedInstanceState); |
||||||
|
mCameraPicker.setPickCallback(new CameraListener(this)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { |
||||||
|
super.onViewCreated(view, savedInstanceState); |
||||||
|
checkPermissionAndLoad(); |
||||||
|
} |
||||||
|
|
||||||
|
private void checkPermissionAndLoad() { |
||||||
|
try { |
||||||
|
if (!BoxingBuilderConfig.TESTING && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M |
||||||
|
&& ContextCompat.checkSelfPermission(getActivity(), STORAGE_PERMISSIONS[0]) != PERMISSION_GRANTED |
||||||
|
&& ContextCompat.checkSelfPermission(getActivity(), STORAGE_PERMISSIONS[1]) != PERMISSION_GRANTED) { |
||||||
|
requestPermissions(STORAGE_PERMISSIONS, REQUEST_CODE_PERMISSION); |
||||||
|
} else { |
||||||
|
startLoading(); |
||||||
|
} |
||||||
|
} catch (IllegalArgumentException | IllegalStateException e) { |
||||||
|
onRequestPermissionError(STORAGE_PERMISSIONS, e); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
||||||
|
if (REQUEST_CODE_PERMISSION == requestCode) { |
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
||||||
|
onRequestPermissionSuc(requestCode, permissions, grantResults); |
||||||
|
} else { |
||||||
|
onRequestPermissionError(permissions, |
||||||
|
new SecurityException("request android.permission.READ_EXTERNAL_STORAGE error.")); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* called when you have input medias, then call {@link #onCreateWithSelectedMedias(Bundle, List)} to get the input medias. |
||||||
|
* |
||||||
|
* @param selectedMedias input medias |
||||||
|
* @return {@link AbsBoxingViewFragment} |
||||||
|
*/ |
||||||
|
public final AbsBoxingViewFragment setSelectedBundle(ArrayList<BaseMedia> selectedMedias) { |
||||||
|
Bundle bundle = new Bundle(); |
||||||
|
if (selectedMedias != null && !selectedMedias.isEmpty()) { |
||||||
|
bundle.putParcelableArrayList(Boxing.EXTRA_SELECTED_MEDIA, selectedMedias); |
||||||
|
} |
||||||
|
setArguments(bundle); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public final void setPresenter(@NonNull PickerContract.Presenter presenter) { |
||||||
|
this.mPresenter = presenter; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the {@link ContentResolver} |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public final ContentResolver getAppCr() { |
||||||
|
return getActivity().getApplicationContext().getContentResolver(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* if {@link AbsBoxingViewFragment} is not working with {@link AbsBoxingActivity}, it needs a listener to call |
||||||
|
* when the jobs done. |
||||||
|
* |
||||||
|
* @param onFinishListener {@link Boxing.OnBoxingFinishListener} |
||||||
|
*/ |
||||||
|
final void setOnFinishListener(Boxing.OnBoxingFinishListener onFinishListener) { |
||||||
|
mOnFinishListener = onFinishListener; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* called the job is done.Click the ok button, take a photo from camera, crop a photo. |
||||||
|
* most of the time, you do not have to override. |
||||||
|
* |
||||||
|
* @param medias the list of selection |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onFinish(@NonNull List<BaseMedia> medias) { |
||||||
|
Intent intent = new Intent(); |
||||||
|
intent.putParcelableArrayListExtra(Boxing.EXTRA_RESULT, (ArrayList<BaseMedia>) medias); |
||||||
|
if (mOnFinishListener != null) { |
||||||
|
mOnFinishListener.onBoxingFinish(intent, medias); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* need crop or not |
||||||
|
* |
||||||
|
* @return true, need it. |
||||||
|
*/ |
||||||
|
public final boolean hasCropBehavior() { |
||||||
|
BoxingConfig config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
return config != null && config.isSingleImageMode() && config.getCropOption() != null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* to start the crop behavior, call it when {@link #hasCropBehavior()} return true. |
||||||
|
* |
||||||
|
* @param media the media to be cropped. |
||||||
|
* @param requestCode The integer request code originally supplied to |
||||||
|
* startActivityForResult(), allowing you to identify who this |
||||||
|
* result came from. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public final void startCrop(@NonNull BaseMedia media, int requestCode) { |
||||||
|
BoxingCropOption cropConfig = BoxingManager.getInstance().getBoxingConfig().getCropOption(); |
||||||
|
BoxingCrop.getInstance().onStartCrop(getActivity(), this, cropConfig, media.getPath(), requestCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set or update the config.most of the time, you do not have to call it. |
||||||
|
* |
||||||
|
* @param config {@link BoxingConfig} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public final void setPickerConfig(BoxingConfig config) { |
||||||
|
if (config == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
BoxingManager.getInstance().setBoxingConfig(config); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) { |
||||||
|
super.onActivityResult(requestCode, resultCode, data); |
||||||
|
if (mCameraPicker != null && requestCode == CameraPickerHelper.REQ_CODE_CAMERA) { |
||||||
|
onCameraActivityResult(requestCode, resultCode); |
||||||
|
} |
||||||
|
if (hasCropBehavior()) { |
||||||
|
onCropActivityResult(requestCode, resultCode, data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSaveInstanceState(Bundle outState) { |
||||||
|
super.onSaveInstanceState(outState); |
||||||
|
if (mCameraPicker != null) { |
||||||
|
mCameraPicker.onSaveInstanceState(outState); |
||||||
|
} |
||||||
|
outState.putParcelable(Boxing.EXTRA_CONFIG, BoxingManager.getInstance().getBoxingConfig()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* in {@link BoxingConfig.Mode#MULTI_IMG}, call this in {@link Fragment#onSaveInstanceState(Bundle)}. |
||||||
|
* |
||||||
|
* @param outState Bundle in which to place your saved state. |
||||||
|
* @param selected the selected medias. |
||||||
|
*/ |
||||||
|
public final void onSaveMedias(Bundle outState, ArrayList<BaseMedia> selected) { |
||||||
|
if (selected != null && !selected.isEmpty()) { |
||||||
|
outState.putParcelableArrayList(Boxing.EXTRA_SELECTED_MEDIA, selected); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* call this to clear resource. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onDestroy() { |
||||||
|
super.onDestroy(); |
||||||
|
if (mPresenter != null) { |
||||||
|
mPresenter.destroy(); |
||||||
|
} |
||||||
|
if (mCameraPicker != null) { |
||||||
|
mCameraPicker.release(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* in {@link BoxingConfig.Mode#MULTI_IMG}, call this to pick the selected medias in all medias. |
||||||
|
*/ |
||||||
|
public final void checkSelectedMedia(List<BaseMedia> allMedias, List<BaseMedia> selectedMedias) { |
||||||
|
mPresenter.checkSelectedMedia(allMedias, selectedMedias); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* load first page of medias. |
||||||
|
* use {@link #showMedia(List, int)} to get the result. |
||||||
|
*/ |
||||||
|
public final void loadMedias() { |
||||||
|
mPresenter.loadMedias(0, AlbumEntity.DEFAULT_NAME); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* load the medias for the specify page and album id. |
||||||
|
* use {@link #showMedia(List, int)} to get the result. |
||||||
|
* |
||||||
|
* @param page page numbers. |
||||||
|
* @param albumId the album id is {@link AlbumEntity#mBucketId}. |
||||||
|
*/ |
||||||
|
public final void loadMedias(int page, String albumId) { |
||||||
|
mPresenter.loadMedias(page, albumId); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* extra call to load albums in database, use {@link #showAlbum(List)} to get result. |
||||||
|
* In {@link BoxingConfig.Mode#VIDEO} it is not necessary. |
||||||
|
*/ |
||||||
|
public void loadAlbum() { |
||||||
|
if (!BoxingManager.getInstance().getBoxingConfig().isVideoMode()) { |
||||||
|
mPresenter.loadAlbums(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public final boolean hasNextPage() { |
||||||
|
return mPresenter.hasNextPage(); |
||||||
|
} |
||||||
|
|
||||||
|
public final boolean canLoadNextPage() { |
||||||
|
return mPresenter.canLoadNextPage(); |
||||||
|
} |
||||||
|
|
||||||
|
public final void onLoadNextPage() { |
||||||
|
mPresenter.onLoadNextPage(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the max count set before |
||||||
|
*/ |
||||||
|
public final int getMaxCount() { |
||||||
|
BoxingConfig config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
if (config == null) { |
||||||
|
return BoxingConfig.DEFAULT_SELECTED_COUNT; |
||||||
|
} |
||||||
|
return config.getMaxCount(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* successfully get result from camera in {@link #onActivityResult(int, int, Intent)}. |
||||||
|
* call this after other operations. |
||||||
|
*/ |
||||||
|
public void onCameraActivityResult(int requestCode, int resultCode) { |
||||||
|
mCameraPicker.onActivityResult(requestCode, resultCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* successfully get result from crop in {@link #onActivityResult(int, int, Intent)} |
||||||
|
*/ |
||||||
|
public void onCropActivityResult(int requestCode, int resultCode, @NonNull Intent data) { |
||||||
|
Uri output = BoxingCrop.getInstance().onCropFinish(resultCode, data); |
||||||
|
if (output != null) { |
||||||
|
List<BaseMedia> medias = new ArrayList<>(1); |
||||||
|
ImageMedia media = new ImageMedia(String.valueOf(System.currentTimeMillis()), output.getPath()); |
||||||
|
medias.add(media); |
||||||
|
onFinish(medias); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* start camera to take a photo. |
||||||
|
* |
||||||
|
* @param activity the caller activity. |
||||||
|
* @param fragment the caller fragment, may be null. |
||||||
|
* @param subFolderPath the folder name in "DCIM/bili/boxing/" |
||||||
|
*/ |
||||||
|
public final void startCamera(Activity activity, Fragment fragment, String subFolderPath) { |
||||||
|
try { |
||||||
|
if (!BoxingBuilderConfig.TESTING && ContextCompat.checkSelfPermission(getActivity(), CAMERA_PERMISSIONS[0]) != PERMISSION_GRANTED) { |
||||||
|
requestPermissions(CAMERA_PERMISSIONS, REQUEST_CODE_PERMISSION); |
||||||
|
} else { |
||||||
|
if (!BoxingManager.getInstance().getBoxingConfig().isVideoMode()) { |
||||||
|
mCameraPicker.startCamera(activity, fragment, subFolderPath); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (IllegalArgumentException | IllegalStateException e) { |
||||||
|
onRequestPermissionError(CAMERA_PERMISSIONS, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static final class CameraListener implements CameraPickerHelper.Callback { |
||||||
|
private WeakReference<AbsBoxingViewFragment> mWr; |
||||||
|
|
||||||
|
CameraListener(AbsBoxingViewFragment fragment) { |
||||||
|
mWr = new WeakReference<>(fragment); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFinish(@NonNull CameraPickerHelper helper) { |
||||||
|
AbsBoxingViewFragment fragment = mWr.get(); |
||||||
|
if (fragment == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
File file = new File(helper.getSourceFilePath()); |
||||||
|
|
||||||
|
if (!file.exists()) { |
||||||
|
onError(helper); |
||||||
|
return; |
||||||
|
} |
||||||
|
ImageMedia cameraMedia = new ImageMedia(file); |
||||||
|
cameraMedia.saveMediaStore(fragment.getAppCr()); |
||||||
|
fragment.onCameraFinish(cameraMedia); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onError(@NonNull CameraPickerHelper helper) { |
||||||
|
AbsBoxingViewFragment fragment = mWr.get(); |
||||||
|
if (fragment == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
fragment.onCameraError(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,255 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing; |
||||||
|
|
||||||
|
import android.annotation.TargetApi; |
||||||
|
import android.app.Activity; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Build; |
||||||
|
import android.os.Bundle; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.fragment.app.Fragment; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.presenter.PickerPresenter; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* An entry for {@link AbsBoxingActivity} and {@link AbsBoxingViewFragment}.<br/> |
||||||
|
* 1.call {@link #of(BoxingConfig)} to pick a mode.<br/> |
||||||
|
* 2.to use {@link AbsBoxingActivity} + {@link AbsBoxingViewFragment} combination, |
||||||
|
* call {@link #withIntent(Context, Class)} to make a intent and {@link #start(Activity)} to start a new Activity.<br/> |
||||||
|
* to use {@link AbsBoxingViewFragment} only, just call {@link #setupFragment(AbsBoxingViewFragment, OnBoxingFinishListener)}.<br/> |
||||||
|
* 3 4.to get result from a new Activity, call {@link #getResult(Intent)} in {@link Activity#onActivityResult(int, int, Intent)}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class Boxing { |
||||||
|
public static final String EXTRA_SELECTED_MEDIA = "com.bilibili.boxing.Boxing.selected_media"; |
||||||
|
public static final String EXTRA_ALBUM_ID = "com.bilibili.boxing.Boxing.album_id"; |
||||||
|
|
||||||
|
static final String EXTRA_CONFIG = "com.bilibili.boxing.Boxing.config"; |
||||||
|
static final String EXTRA_RESULT = "com.bilibili.boxing.Boxing.result"; |
||||||
|
static final String EXTRA_START_POS = "com.bilibili.boxing.Boxing.start_pos"; |
||||||
|
|
||||||
|
private Intent mIntent; |
||||||
|
|
||||||
|
private Boxing(BoxingConfig config) { |
||||||
|
BoxingManager.getInstance().setBoxingConfig(config); |
||||||
|
this.mIntent = new Intent(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the media result. |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public static ArrayList<BaseMedia> getResult(Intent data) { |
||||||
|
if (data != null) { |
||||||
|
return data.getParcelableArrayListExtra(EXTRA_RESULT); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* call {@link #of(BoxingConfig)} first to specify the mode otherwise {@link BoxingConfig.Mode#MULTI_IMG} is used.<br/> |
||||||
|
*/ |
||||||
|
public static Boxing get() { |
||||||
|
BoxingConfig config = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
if (config == null) { |
||||||
|
config = new BoxingConfig(BoxingConfig.Mode.MULTI_IMG).needGif(); |
||||||
|
BoxingManager.getInstance().setBoxingConfig(config); |
||||||
|
} |
||||||
|
return new Boxing(config); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* create a boxing entry. |
||||||
|
* |
||||||
|
* @param config {@link BoxingConfig} |
||||||
|
*/ |
||||||
|
public static Boxing of(BoxingConfig config) { |
||||||
|
return new Boxing(config); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* create a boxing entry. |
||||||
|
* |
||||||
|
* @param mode {@link BoxingConfig.Mode} |
||||||
|
*/ |
||||||
|
public static Boxing of(BoxingConfig.Mode mode) { |
||||||
|
return new Boxing(new BoxingConfig(mode)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* create a boxing entry. use {@link BoxingConfig.Mode#MULTI_IMG}. |
||||||
|
*/ |
||||||
|
public static Boxing of() { |
||||||
|
BoxingConfig config = new BoxingConfig(BoxingConfig.Mode.MULTI_IMG).needGif(); |
||||||
|
return new Boxing(config); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the intent build by boxing after call {@link #withIntent}. |
||||||
|
*/ |
||||||
|
public Intent getIntent() { |
||||||
|
return mIntent; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* same as {@link Intent#setClass(Context, Class)} |
||||||
|
*/ |
||||||
|
public Boxing withIntent(Context context, Class<?> cls) { |
||||||
|
return withIntent(context, cls, null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link Intent#setClass(Context, Class)} with input medias. |
||||||
|
*/ |
||||||
|
public Boxing withIntent(Context context, Class<?> cls, ArrayList<? extends BaseMedia> selectedMedias) { |
||||||
|
mIntent.setClass(context, cls); |
||||||
|
if (selectedMedias != null && !selectedMedias.isEmpty()) { |
||||||
|
mIntent.putExtra(EXTRA_SELECTED_MEDIA, selectedMedias); |
||||||
|
} |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* use to start image viewer. |
||||||
|
* |
||||||
|
* @param medias selected medias. |
||||||
|
* @param pos the start position. |
||||||
|
*/ |
||||||
|
public Boxing withIntent(Context context, Class<?> cls, ArrayList<? extends BaseMedia> medias, int pos) { |
||||||
|
withIntent(context, cls, medias, pos, ""); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* use to start image viewer. |
||||||
|
* |
||||||
|
* @param medias selected medias. |
||||||
|
* @param pos the start position. |
||||||
|
* @param albumId the specify album id. |
||||||
|
*/ |
||||||
|
public Boxing withIntent(Context context, Class<?> cls, ArrayList<? extends BaseMedia> medias, int pos, String albumId) { |
||||||
|
mIntent.setClass(context, cls); |
||||||
|
if (medias != null && !medias.isEmpty()) { |
||||||
|
mIntent.putExtra(EXTRA_SELECTED_MEDIA, medias); |
||||||
|
} |
||||||
|
if (pos >= 0) { |
||||||
|
mIntent.putExtra(EXTRA_START_POS, pos); |
||||||
|
} |
||||||
|
if (albumId != null) { |
||||||
|
mIntent.putExtra(EXTRA_ALBUM_ID, albumId); |
||||||
|
} |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* same as {@link Activity#startActivity(Intent)} |
||||||
|
*/ |
||||||
|
public void start(@NonNull Activity activity) { |
||||||
|
activity.startActivity(mIntent); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* use to start raw image viewer. |
||||||
|
* |
||||||
|
* @param viewMode {@link BoxingConfig.ViewMode} |
||||||
|
*/ |
||||||
|
public void start(@NonNull Activity activity, BoxingConfig.ViewMode viewMode) { |
||||||
|
BoxingManager.getInstance().getBoxingConfig().withViewer(viewMode); |
||||||
|
activity.startActivity(mIntent); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* same as {@link Activity#startActivityForResult(Intent, int, Bundle)} |
||||||
|
*/ |
||||||
|
public void start(@NonNull Activity activity, int requestCode) { |
||||||
|
activity.startActivityForResult(mIntent, requestCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* same as {@link Fragment#startActivityForResult(Intent, int, Bundle)} |
||||||
|
*/ |
||||||
|
public void start(@NonNull Fragment fragment, int requestCode) { |
||||||
|
fragment.startActivityForResult(mIntent, requestCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* use to start raw image viewer. |
||||||
|
* |
||||||
|
* @param viewMode {@link BoxingConfig.ViewMode} |
||||||
|
*/ |
||||||
|
public void start(@NonNull Fragment fragment, int requestCode, BoxingConfig.ViewMode viewMode) { |
||||||
|
BoxingManager.getInstance().getBoxingConfig().withViewer(viewMode); |
||||||
|
fragment.startActivityForResult(mIntent, requestCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* same as {@link android.app.Fragment#startActivityForResult(Intent, int, Bundle)} |
||||||
|
*/ |
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB) |
||||||
|
public void start(@NonNull android.app.Fragment fragment, int requestCode) { |
||||||
|
fragment.startActivityForResult(mIntent, requestCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* use to start raw image viewer. |
||||||
|
* |
||||||
|
* @param viewMode {@link BoxingConfig.ViewMode} |
||||||
|
*/ |
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB) |
||||||
|
public void start(@NonNull android.app.Fragment fragment, int requestCode, BoxingConfig.ViewMode viewMode) { |
||||||
|
BoxingManager.getInstance().getBoxingConfig().withViewer(viewMode); |
||||||
|
fragment.startActivityForResult(mIntent, requestCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set up a subclass of {@link AbsBoxingViewFragment} without a {@link AbsBoxingActivity}. |
||||||
|
* |
||||||
|
* @param fragment subclass of {@link AbsBoxingViewFragment} |
||||||
|
* @param onFinishListener a listener fo media result |
||||||
|
*/ |
||||||
|
public void setupFragment(@NonNull AbsBoxingViewFragment fragment, OnBoxingFinishListener onFinishListener) { |
||||||
|
fragment.setPresenter(new PickerPresenter(fragment)); |
||||||
|
fragment.setOnFinishListener(onFinishListener); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* work with a subclass of {@link AbsBoxingViewFragment} without a {@link AbsBoxingActivity}. |
||||||
|
*/ |
||||||
|
public interface OnBoxingFinishListener { |
||||||
|
|
||||||
|
/** |
||||||
|
* live with {@link com.bilibili.boxing.presenter.PickerContract.View#onFinish(List)} |
||||||
|
* |
||||||
|
* @param medias the selection of medias. |
||||||
|
*/ |
||||||
|
void onBoxingFinish(Intent intent, @Nullable List<BaseMedia> medias); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing; |
||||||
|
|
||||||
|
import android.app.Activity; |
||||||
|
import android.content.Intent; |
||||||
|
import android.net.Uri; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.fragment.app.Fragment; |
||||||
|
|
||||||
|
import com.bilibili.boxing.loader.IBoxingCrop; |
||||||
|
import com.bilibili.boxing.model.config.BoxingCropOption; |
||||||
|
|
||||||
|
/** |
||||||
|
* A loader holding {@link IBoxingCrop} to crop images. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingCrop { |
||||||
|
private static final BoxingCrop INSTANCE = new BoxingCrop(); |
||||||
|
private IBoxingCrop mCrop; |
||||||
|
|
||||||
|
private BoxingCrop() { |
||||||
|
} |
||||||
|
|
||||||
|
public static BoxingCrop getInstance() { |
||||||
|
return INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public void init(@NonNull IBoxingCrop loader) { |
||||||
|
this.mCrop = loader; |
||||||
|
} |
||||||
|
|
||||||
|
public void onStartCrop(Activity activity, Fragment fragment, @NonNull BoxingCropOption cropConfig, |
||||||
|
@NonNull String path, int requestCode) { |
||||||
|
if (ensureLoader()) { |
||||||
|
throw new IllegalStateException("init method should be called first"); |
||||||
|
} |
||||||
|
if (cropConfig == null) { |
||||||
|
throw new IllegalArgumentException("crop config is null."); |
||||||
|
} |
||||||
|
mCrop.onStartCrop(activity, fragment, cropConfig, path, requestCode); |
||||||
|
} |
||||||
|
|
||||||
|
public Uri onCropFinish(int resultCode, Intent data) { |
||||||
|
if (ensureLoader()) { |
||||||
|
throw new IllegalStateException("init method should be called first"); |
||||||
|
} |
||||||
|
return mCrop.onCropFinish(resultCode, data); |
||||||
|
} |
||||||
|
|
||||||
|
public IBoxingCrop getCrop() { |
||||||
|
return mCrop; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean ensureLoader() { |
||||||
|
return mCrop == null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import android.widget.ImageView; |
||||||
|
|
||||||
|
import com.bilibili.boxing.loader.IBoxingCallback; |
||||||
|
import com.bilibili.boxing.loader.IBoxingMediaLoader; |
||||||
|
|
||||||
|
/** |
||||||
|
* A loader holding {@link IBoxingMediaLoader} to displayThumbnail medias. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingMediaLoader { |
||||||
|
private static final BoxingMediaLoader INSTANCE = new BoxingMediaLoader(); |
||||||
|
private IBoxingMediaLoader mLoader; |
||||||
|
|
||||||
|
private BoxingMediaLoader() { |
||||||
|
} |
||||||
|
|
||||||
|
public static BoxingMediaLoader getInstance() { |
||||||
|
return INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public void init(@NonNull IBoxingMediaLoader loader) { |
||||||
|
this.mLoader = loader; |
||||||
|
} |
||||||
|
|
||||||
|
public void displayThumbnail(@NonNull ImageView img, @NonNull String path, int width, int height) { |
||||||
|
if (ensureLoader()) { |
||||||
|
throw new IllegalStateException("init method should be called first"); |
||||||
|
} |
||||||
|
mLoader.displayThumbnail(img, path, width, height); |
||||||
|
} |
||||||
|
|
||||||
|
public void displayRaw(@NonNull ImageView img, @NonNull String path, int width, int height, IBoxingCallback callback) { |
||||||
|
if (ensureLoader()) { |
||||||
|
throw new IllegalStateException("init method should be called first"); |
||||||
|
} |
||||||
|
mLoader.displayRaw(img, path, width, height, callback); |
||||||
|
} |
||||||
|
|
||||||
|
public IBoxingMediaLoader getLoader() { |
||||||
|
return mLoader; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean ensureLoader() { |
||||||
|
return mLoader == null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.loader; |
||||||
|
|
||||||
|
/** |
||||||
|
* Simple callback only cares about success/fail. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public interface IBoxingCallback { |
||||||
|
|
||||||
|
/** |
||||||
|
* Successfully handle a task; |
||||||
|
*/ |
||||||
|
void onSuccess(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Error happened when running a task; |
||||||
|
*/ |
||||||
|
void onFail(Throwable t); |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.loader; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.content.Intent; |
||||||
|
import android.net.Uri; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.config.BoxingCropOption; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.fragment.app.Fragment; |
||||||
|
|
||||||
|
/** |
||||||
|
* Cropping interface. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public interface IBoxingCrop { |
||||||
|
|
||||||
|
/*** |
||||||
|
* start crop operation. |
||||||
|
* |
||||||
|
* @param cropConfig {@link BoxingCropOption} |
||||||
|
* @param path the absolute path of media. |
||||||
|
* @param requestCode request code for the crop. |
||||||
|
*/ |
||||||
|
void onStartCrop(Context context, Fragment fragment, @NonNull BoxingCropOption cropConfig, |
||||||
|
@NonNull String path, int requestCode); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* get the result of cropping. |
||||||
|
* |
||||||
|
* @param resultCode the code in {@link android.app.Activity#onActivityResult(int, int, Intent)} |
||||||
|
* @param data the data intent |
||||||
|
* @return the cropped image uri. |
||||||
|
*/ |
||||||
|
Uri onCropFinish(int resultCode, Intent data); |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.loader; |
||||||
|
|
||||||
|
import android.widget.ImageView; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* Define how media display. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public interface IBoxingMediaLoader { |
||||||
|
/** |
||||||
|
* display thumbnail images for a ImageView. |
||||||
|
* |
||||||
|
* @param img the display ImageView. Through ImageView.getTag(R.string.boxing_app_name) to get the absolute path of the exact path to display. |
||||||
|
* @param absPath the absolute path to display, may be out of date when fast scrolling. |
||||||
|
* @param width the resize with for the image. |
||||||
|
* @param height the resize height for the image. |
||||||
|
*/ |
||||||
|
void displayThumbnail(@NonNull ImageView img, @NonNull String absPath, int width, int height); |
||||||
|
|
||||||
|
/** |
||||||
|
* display raw images for a ImageView, need more work to do. |
||||||
|
* |
||||||
|
* @param img the display ImageView.Through ImageView.getTag(R.string.boxing_app_name) to get the absolute path of the exact path to display. |
||||||
|
* @param absPath the absolute path to display, may be out of date when fast scrolling. |
||||||
|
* @param width the expected width, 0 means the raw width. |
||||||
|
* @param height the expected height, 0 means the raw height. |
||||||
|
* @param callback the callback for the load result. |
||||||
|
*/ |
||||||
|
void displayRaw(@NonNull ImageView img, @NonNull String absPath, int width, int height, IBoxingCallback callback); |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model; |
||||||
|
|
||||||
|
/** |
||||||
|
* Marks for building and running |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingBuilderConfig { |
||||||
|
/** |
||||||
|
* mark for debug |
||||||
|
*/ |
||||||
|
public static final boolean DEBUG = false; |
||||||
|
|
||||||
|
/** |
||||||
|
* mark for unit testing |
||||||
|
*/ |
||||||
|
public static final boolean TESTING = false; |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.callback.IAlbumTaskCallback; |
||||||
|
import com.bilibili.boxing.model.callback.IMediaTaskCallback; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.task.IMediaTask; |
||||||
|
import com.bilibili.boxing.model.task.impl.AlbumTask; |
||||||
|
import com.bilibili.boxing.model.task.impl.ImageTask; |
||||||
|
import com.bilibili.boxing.model.task.impl.VideoTask; |
||||||
|
import com.bilibili.boxing.utils.BoxingExecutor; |
||||||
|
|
||||||
|
/** |
||||||
|
* The Manager to load {@link IMediaTask} and {@link AlbumTask}, holding {@link BoxingConfig}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingManager { |
||||||
|
private static final BoxingManager INSTANCE = new BoxingManager(); |
||||||
|
|
||||||
|
private BoxingConfig mConfig; |
||||||
|
|
||||||
|
private BoxingManager() { |
||||||
|
} |
||||||
|
|
||||||
|
public static BoxingManager getInstance() { |
||||||
|
return INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public void setBoxingConfig(BoxingConfig config) { |
||||||
|
mConfig = config; |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingConfig getBoxingConfig() { |
||||||
|
return mConfig; |
||||||
|
} |
||||||
|
|
||||||
|
public void loadMedia(@NonNull final ContentResolver cr, final int page, |
||||||
|
final String id, @NonNull final IMediaTaskCallback callback) { |
||||||
|
final IMediaTask task = mConfig.isVideoMode() ? new VideoTask() : new ImageTask(); |
||||||
|
BoxingExecutor.getInstance().runWorker(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
task.load(cr, page, id, callback); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void loadAlbum(@NonNull final ContentResolver cr, @NonNull final IAlbumTaskCallback callback) { |
||||||
|
BoxingExecutor.getInstance().runWorker(new Runnable() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
new AlbumTask().start(cr, callback); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.callback; |
||||||
|
|
||||||
|
|
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.Nullable; |
||||||
|
|
||||||
|
/** |
||||||
|
* A callback for load album. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public interface IAlbumTaskCallback { |
||||||
|
|
||||||
|
/** |
||||||
|
* get all album in database |
||||||
|
* |
||||||
|
* @param list album list |
||||||
|
*/ |
||||||
|
void postAlbumList(@Nullable List<AlbumEntity> list); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.callback; |
||||||
|
|
||||||
|
|
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.Nullable; |
||||||
|
|
||||||
|
/** |
||||||
|
* A callback to load {@link BaseMedia}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public interface IMediaTaskCallback<T extends BaseMedia> { |
||||||
|
/** |
||||||
|
* get a page of medias in a album |
||||||
|
* |
||||||
|
* @param medias page of medias |
||||||
|
* @param count the count for the photo in album |
||||||
|
*/ |
||||||
|
void postMedia(@Nullable List<T> medias, int count); |
||||||
|
|
||||||
|
/** |
||||||
|
* judge the path needing filer |
||||||
|
* |
||||||
|
* @param path photo path |
||||||
|
* @return true:be filter |
||||||
|
*/ |
||||||
|
boolean needFilter(String path); |
||||||
|
} |
@ -0,0 +1,319 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.config; |
||||||
|
|
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes; |
||||||
|
|
||||||
|
/** |
||||||
|
* The pick config.<br/> |
||||||
|
* 1.{@link Mode} is necessary. <br/> |
||||||
|
* 2.specify functions: camera, gif, paging. <br/> |
||||||
|
* calling {@link #needCamera(int)} to displayThumbnail a camera icon. <br/> |
||||||
|
* calling {@link #needGif()} to displayThumbnail gif photos. <br/> |
||||||
|
* calling {@link #needPaging(boolean)} to create load medias page by page, by default is true. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingConfig implements Parcelable { |
||||||
|
public static final int DEFAULT_SELECTED_COUNT = 9; |
||||||
|
|
||||||
|
private Mode mMode = Mode.SINGLE_IMG; |
||||||
|
private ViewMode mViewMode = ViewMode.PREVIEW; |
||||||
|
private BoxingCropOption mCropOption; |
||||||
|
|
||||||
|
private int mMediaPlaceHolderRes; |
||||||
|
private int mMediaCheckedRes; |
||||||
|
private int mMediaUnCheckedRes; |
||||||
|
private int mAlbumPlaceHolderRes; |
||||||
|
private int mVideoDurationRes; |
||||||
|
private int mCameraRes; |
||||||
|
|
||||||
|
private boolean mNeedCamera; |
||||||
|
private boolean mNeedGif; |
||||||
|
private boolean mNeedPaging = true; |
||||||
|
|
||||||
|
private int mMaxCount = DEFAULT_SELECTED_COUNT; |
||||||
|
|
||||||
|
public enum Mode { |
||||||
|
SINGLE_IMG, MULTI_IMG, VIDEO |
||||||
|
} |
||||||
|
|
||||||
|
public enum ViewMode { |
||||||
|
PREVIEW, EDIT, PRE_EDIT |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingConfig() { |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingConfig(Mode mode) { |
||||||
|
this.mMode = mode; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isNeedCamera() { |
||||||
|
return mNeedCamera; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isNeedPaging() { |
||||||
|
return mNeedPaging; |
||||||
|
} |
||||||
|
|
||||||
|
public Mode getMode() { |
||||||
|
return mMode; |
||||||
|
} |
||||||
|
|
||||||
|
public ViewMode getViewMode() { |
||||||
|
return mViewMode; |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingCropOption getCropOption() { |
||||||
|
return mCropOption; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the max count set by {@link #withMaxCount(int)}, otherwise return 9. |
||||||
|
*/ |
||||||
|
public int getMaxCount() { |
||||||
|
if (mMaxCount > 0) { |
||||||
|
return mMaxCount; |
||||||
|
} |
||||||
|
return DEFAULT_SELECTED_COUNT; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the image drawable resource by {@link BoxingConfig#withMediaPlaceHolderRes(int)}. |
||||||
|
* @return >0, set a valid drawable resource; otherwise without a placeholder. |
||||||
|
*/ |
||||||
|
public @DrawableRes |
||||||
|
int getMediaPlaceHolderRes() { |
||||||
|
return mMediaPlaceHolderRes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the media checked drawable resource by {@link BoxingConfig#withMediaCheckedRes(int)}. |
||||||
|
* @return >0, set a valid drawable resource; otherwise without a placeholder. |
||||||
|
*/ |
||||||
|
public @DrawableRes int getMediaCheckedRes() { |
||||||
|
return mMediaCheckedRes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the media unchecked drawable resource by {@link BoxingConfig#withMediaUncheckedRes(int)} (int)}. |
||||||
|
* @return >0, set a valid drawable resource; otherwise without a placeholder. |
||||||
|
*/ |
||||||
|
public @DrawableRes int getMediaUnCheckedRes() { |
||||||
|
return mMediaUnCheckedRes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the media unchecked drawable resource by {@link BoxingConfig#withMediaPlaceHolderRes(int)}. |
||||||
|
* @return >0, set a valid drawable resource; otherwise without a placeholder. |
||||||
|
*/ |
||||||
|
public @DrawableRes int getCameraRes() { |
||||||
|
return mCameraRes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the album drawable resource by {@link BoxingConfig#withAlbumPlaceHolderRes(int)}. |
||||||
|
* @return >0, set a valid drawable resource; otherwise without a placeholder. |
||||||
|
*/ |
||||||
|
public @DrawableRes int getAlbumPlaceHolderRes() { |
||||||
|
return mAlbumPlaceHolderRes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the video drawable resource by {@link BoxingConfig#withVideoDurationRes(int)}. |
||||||
|
* @return >0, set a valid drawable resource; otherwise without a placeholder. |
||||||
|
*/ |
||||||
|
public @DrawableRes int getVideoDurationRes() { |
||||||
|
return mVideoDurationRes; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isNeedLoading() { |
||||||
|
return mViewMode == ViewMode.EDIT; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isNeedEdit() { |
||||||
|
return mViewMode != ViewMode.PREVIEW; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isVideoMode() { |
||||||
|
return mMode == Mode.VIDEO; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isMultiImageMode() { |
||||||
|
return mMode == Mode.MULTI_IMG; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isSingleImageMode() { |
||||||
|
return mMode == Mode.SINGLE_IMG; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isNeedGif() { |
||||||
|
return mNeedGif; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* call this means gif is needed. |
||||||
|
*/ |
||||||
|
public BoxingConfig needGif() { |
||||||
|
this.mNeedGif = true; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set the camera res. |
||||||
|
*/ |
||||||
|
public BoxingConfig needCamera(@DrawableRes int cameraRes) { |
||||||
|
this.mCameraRes = cameraRes; |
||||||
|
this.mNeedCamera = true; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* call this means paging is needed,by default is true. |
||||||
|
*/ |
||||||
|
public BoxingConfig needPaging(boolean needPaging) { |
||||||
|
this.mNeedPaging = needPaging; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingConfig withViewer(ViewMode viewMode) { |
||||||
|
this.mViewMode = viewMode; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingConfig withCropOption(BoxingCropOption cropOption) { |
||||||
|
this.mCropOption = cropOption; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set the max count of selected medias in {@link Mode#MULTI_IMG} |
||||||
|
* @param count max count |
||||||
|
*/ |
||||||
|
public BoxingConfig withMaxCount(int count) { |
||||||
|
if (count < 1) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
this.mMaxCount = count; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set the image placeholder, default 0 |
||||||
|
*/ |
||||||
|
public BoxingConfig withMediaPlaceHolderRes(@DrawableRes int mediaPlaceHolderRes) { |
||||||
|
this.mMediaPlaceHolderRes = mediaPlaceHolderRes; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set the image placeholder, otherwise use default drawable. |
||||||
|
*/ |
||||||
|
public BoxingConfig withMediaCheckedRes(@DrawableRes int mediaCheckedResRes) { |
||||||
|
this.mMediaCheckedRes = mediaCheckedResRes; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set the image placeholder, otherwise use default drawable. |
||||||
|
*/ |
||||||
|
public BoxingConfig withMediaUncheckedRes(@DrawableRes int mediaUncheckedRes) { |
||||||
|
this.mMediaUnCheckedRes = mediaUncheckedRes; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set the album placeholder, default 0 |
||||||
|
*/ |
||||||
|
public BoxingConfig withAlbumPlaceHolderRes(@DrawableRes int albumPlaceHolderRes) { |
||||||
|
this.mAlbumPlaceHolderRes = albumPlaceHolderRes; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* set the video duration resource in video mode, default 0 |
||||||
|
*/ |
||||||
|
public BoxingConfig withVideoDurationRes(@DrawableRes int videoDurationRes) { |
||||||
|
this.mVideoDurationRes = videoDurationRes; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "BoxingConfig{" + |
||||||
|
"mMode=" + mMode + |
||||||
|
", mViewMode=" + mViewMode + |
||||||
|
'}'; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
dest.writeInt(this.mMode == null ? -1 : this.mMode.ordinal()); |
||||||
|
dest.writeInt(this.mViewMode == null ? -1 : this.mViewMode.ordinal()); |
||||||
|
dest.writeParcelable(this.mCropOption, flags); |
||||||
|
dest.writeInt(this.mMediaPlaceHolderRes); |
||||||
|
dest.writeInt(this.mMediaCheckedRes); |
||||||
|
dest.writeInt(this.mMediaUnCheckedRes); |
||||||
|
dest.writeInt(this.mAlbumPlaceHolderRes); |
||||||
|
dest.writeInt(this.mVideoDurationRes); |
||||||
|
dest.writeInt(this.mCameraRes); |
||||||
|
dest.writeByte(this.mNeedCamera ? (byte) 1 : (byte) 0); |
||||||
|
dest.writeByte(this.mNeedGif ? (byte) 1 : (byte) 0); |
||||||
|
dest.writeByte(this.mNeedPaging ? (byte) 1 : (byte) 0); |
||||||
|
dest.writeInt(this.mMaxCount); |
||||||
|
} |
||||||
|
|
||||||
|
protected BoxingConfig(Parcel in) { |
||||||
|
int tmpMMode = in.readInt(); |
||||||
|
this.mMode = tmpMMode == -1 ? null : Mode.values()[tmpMMode]; |
||||||
|
int tmpMViewMode = in.readInt(); |
||||||
|
this.mViewMode = tmpMViewMode == -1 ? null : ViewMode.values()[tmpMViewMode]; |
||||||
|
this.mCropOption = in.readParcelable(BoxingCropOption.class.getClassLoader()); |
||||||
|
this.mMediaPlaceHolderRes = in.readInt(); |
||||||
|
this.mMediaCheckedRes = in.readInt(); |
||||||
|
this.mMediaUnCheckedRes = in.readInt(); |
||||||
|
this.mAlbumPlaceHolderRes = in.readInt(); |
||||||
|
this.mVideoDurationRes = in.readInt(); |
||||||
|
this.mCameraRes = in.readInt(); |
||||||
|
this.mNeedCamera = in.readByte() != 0; |
||||||
|
this.mNeedGif = in.readByte() != 0; |
||||||
|
this.mNeedPaging = in.readByte() != 0; |
||||||
|
this.mMaxCount = in.readInt(); |
||||||
|
} |
||||||
|
|
||||||
|
public static final Creator<BoxingConfig> CREATOR = new Creator<BoxingConfig>() { |
||||||
|
@Override |
||||||
|
public BoxingConfig createFromParcel(Parcel source) { |
||||||
|
return new BoxingConfig(source); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoxingConfig[] newArray(int size) { |
||||||
|
return new BoxingConfig[size]; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,118 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.config; |
||||||
|
|
||||||
|
import android.net.Uri; |
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* The cropping config, a cropped photo uri is needed at least. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingCropOption implements Parcelable { |
||||||
|
private Uri mDestination; |
||||||
|
private float mAspectRatioX; |
||||||
|
private float mAspectRatioY; |
||||||
|
private int mMaxWidth; |
||||||
|
private int mMaxHeight; |
||||||
|
|
||||||
|
public BoxingCropOption(Uri destination) { |
||||||
|
this.mDestination = destination; |
||||||
|
} |
||||||
|
|
||||||
|
public static BoxingCropOption with(@NonNull Uri destination) { |
||||||
|
return new BoxingCropOption(destination); |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingCropOption aspectRatio(float x, float y) { |
||||||
|
this.mAspectRatioX = x; |
||||||
|
this.mAspectRatioY = y; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingCropOption useSourceImageAspectRatio() { |
||||||
|
this.mAspectRatioX = 0; |
||||||
|
this.mAspectRatioY = 0; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public BoxingCropOption withMaxResultSize(int width, int height) { |
||||||
|
this.mMaxWidth = width; |
||||||
|
this.mMaxHeight = height; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public float getAspectRatioX() { |
||||||
|
return mAspectRatioX; |
||||||
|
} |
||||||
|
|
||||||
|
public float getAspectRatioY() { |
||||||
|
return mAspectRatioY; |
||||||
|
} |
||||||
|
|
||||||
|
public int getMaxHeight() { |
||||||
|
return mMaxHeight; |
||||||
|
} |
||||||
|
|
||||||
|
public int getMaxWidth() { |
||||||
|
return mMaxWidth; |
||||||
|
} |
||||||
|
|
||||||
|
public Uri getDestination() { |
||||||
|
return mDestination; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
dest.writeParcelable(this.mDestination, flags); |
||||||
|
dest.writeFloat(this.mAspectRatioX); |
||||||
|
dest.writeFloat(this.mAspectRatioY); |
||||||
|
dest.writeInt(this.mMaxWidth); |
||||||
|
dest.writeInt(this.mMaxHeight); |
||||||
|
} |
||||||
|
|
||||||
|
BoxingCropOption(Parcel in) { |
||||||
|
this.mDestination = in.readParcelable(Uri.class.getClassLoader()); |
||||||
|
this.mAspectRatioX = in.readFloat(); |
||||||
|
this.mAspectRatioY = in.readFloat(); |
||||||
|
this.mMaxWidth = in.readInt(); |
||||||
|
this.mMaxHeight = in.readInt(); |
||||||
|
} |
||||||
|
|
||||||
|
public static final Creator<BoxingCropOption> CREATOR = new Creator<BoxingCropOption>() { |
||||||
|
@Override |
||||||
|
public BoxingCropOption createFromParcel(Parcel source) { |
||||||
|
return new BoxingCropOption(source); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoxingCropOption[] newArray(int size) { |
||||||
|
return new BoxingCropOption[size]; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,102 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.entity; |
||||||
|
|
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* An entity for album. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class AlbumEntity implements Parcelable { |
||||||
|
public static final String DEFAULT_NAME = ""; |
||||||
|
|
||||||
|
public int mCount; |
||||||
|
public boolean mIsSelected; |
||||||
|
|
||||||
|
public String mBucketId; |
||||||
|
public String mBucketName; |
||||||
|
public List<BaseMedia> mImageList; |
||||||
|
|
||||||
|
public AlbumEntity() { |
||||||
|
mCount = 0; |
||||||
|
mImageList = new ArrayList<>(); |
||||||
|
mIsSelected = false; |
||||||
|
} |
||||||
|
|
||||||
|
public static AlbumEntity createDefaultAlbum() { |
||||||
|
AlbumEntity result = new AlbumEntity(); |
||||||
|
result.mBucketId = DEFAULT_NAME; |
||||||
|
result.mIsSelected = true; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean hasImages() { |
||||||
|
return mImageList != null && mImageList.size() > 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "AlbumEntity{" + |
||||||
|
"mCount=" + mCount + |
||||||
|
", mBucketName='" + mBucketName + '\'' + |
||||||
|
", mImageList=" + mImageList + |
||||||
|
'}'; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
dest.writeString(this.mBucketId); |
||||||
|
dest.writeInt(this.mCount); |
||||||
|
dest.writeString(this.mBucketName); |
||||||
|
dest.writeList(this.mImageList); |
||||||
|
dest.writeByte(this.mIsSelected ? (byte) 1 : (byte) 0); |
||||||
|
} |
||||||
|
|
||||||
|
protected AlbumEntity(Parcel in) { |
||||||
|
this.mBucketId = in.readString(); |
||||||
|
this.mCount = in.readInt(); |
||||||
|
this.mBucketName = in.readString(); |
||||||
|
this.mImageList = new ArrayList<>(); |
||||||
|
in.readList(this.mImageList, BaseMedia.class.getClassLoader()); |
||||||
|
this.mIsSelected = in.readByte() != 0; |
||||||
|
} |
||||||
|
|
||||||
|
public static final Creator<AlbumEntity> CREATOR = new Creator<AlbumEntity>() { |
||||||
|
@Override |
||||||
|
public AlbumEntity createFromParcel(Parcel source) { |
||||||
|
return new AlbumEntity(source); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AlbumEntity[] newArray(int size) { |
||||||
|
return new AlbumEntity[size]; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.entity; |
||||||
|
|
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
|
||||||
|
/** |
||||||
|
* The base entity for media. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public abstract class BaseMedia implements Parcelable { |
||||||
|
protected enum TYPE { |
||||||
|
IMAGE, VIDEO |
||||||
|
} |
||||||
|
|
||||||
|
protected String mPath; |
||||||
|
protected String mId; |
||||||
|
protected String mSize; |
||||||
|
|
||||||
|
public BaseMedia() { |
||||||
|
} |
||||||
|
|
||||||
|
public BaseMedia(String id, String path) { |
||||||
|
mId = id; |
||||||
|
mPath = path; |
||||||
|
} |
||||||
|
|
||||||
|
public abstract TYPE getType(); |
||||||
|
|
||||||
|
public String getId() { |
||||||
|
return mId; |
||||||
|
} |
||||||
|
|
||||||
|
public long getSize() { |
||||||
|
try { |
||||||
|
long result = Long.parseLong(mSize); |
||||||
|
return result > 0 ? result : 0; |
||||||
|
}catch (NumberFormatException size) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setId(String id) { |
||||||
|
mId = id; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSize(String size) { |
||||||
|
mSize = size; |
||||||
|
} |
||||||
|
|
||||||
|
public String getPath(){ |
||||||
|
return mPath; |
||||||
|
} |
||||||
|
|
||||||
|
public void setPath(String path) { |
||||||
|
mPath = path; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
dest.writeString(this.mPath); |
||||||
|
dest.writeString(this.mId); |
||||||
|
dest.writeString(this.mSize); |
||||||
|
} |
||||||
|
|
||||||
|
protected BaseMedia(Parcel in) { |
||||||
|
this.mPath = in.readString(); |
||||||
|
this.mId = in.readString(); |
||||||
|
this.mSize = in.readString(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,340 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.entity.impl; |
||||||
|
|
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.content.ContentValues; |
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
import android.provider.MediaStore; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.utils.BoxingExecutor; |
||||||
|
import com.bilibili.boxing.utils.BoxingExifHelper; |
||||||
|
import com.bilibili.boxing.utils.BoxingFileHelper; |
||||||
|
import com.bilibili.boxing.utils.CompressTask; |
||||||
|
import com.bilibili.boxing.utils.ImageCompressor; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Id and absolute path is necessary.Builder Mode can be used too. |
||||||
|
* compress image through {@link #compress(ImageCompressor)}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class ImageMedia extends BaseMedia implements Parcelable { |
||||||
|
private static final long MAX_GIF_SIZE = 1024 * 1024L; |
||||||
|
private static final long MAX_IMAGE_SIZE = 1024 * 1024L; |
||||||
|
|
||||||
|
private boolean mIsSelected; |
||||||
|
private String mThumbnailPath; |
||||||
|
private String mCompressPath; |
||||||
|
private int mHeight; |
||||||
|
private int mWidth; |
||||||
|
private IMAGE_TYPE mImageType; |
||||||
|
private String mMimeType; |
||||||
|
|
||||||
|
public enum IMAGE_TYPE { |
||||||
|
PNG, JPG, GIF |
||||||
|
} |
||||||
|
|
||||||
|
public ImageMedia(String id, String imagePath) { |
||||||
|
super(id, imagePath); |
||||||
|
} |
||||||
|
|
||||||
|
public ImageMedia(@NonNull File file) { |
||||||
|
this.mId = String.valueOf(System.currentTimeMillis()); |
||||||
|
this.mPath = file.getAbsolutePath(); |
||||||
|
this.mSize = String.valueOf(file.length()); |
||||||
|
this.mIsSelected = true; |
||||||
|
} |
||||||
|
|
||||||
|
public ImageMedia(Builder builder) { |
||||||
|
super(builder.mId, builder.mImagePath); |
||||||
|
this.mThumbnailPath = builder.mThumbnailPath; |
||||||
|
this.mSize = builder.mSize; |
||||||
|
this.mHeight = builder.mHeight; |
||||||
|
this.mIsSelected = builder.mIsSelected; |
||||||
|
this.mWidth = builder.mWidth; |
||||||
|
this.mMimeType = builder.mMimeType; |
||||||
|
this.mImageType = getImageTypeByMime(builder.mMimeType); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public TYPE getType() { |
||||||
|
return TYPE.IMAGE; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isSelected() { |
||||||
|
return mIsSelected; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSelected(boolean selected) { |
||||||
|
mIsSelected = selected; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isGifOverSize() { |
||||||
|
return isGif() && getSize() > MAX_GIF_SIZE; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isGif() { |
||||||
|
return getImageType() == IMAGE_TYPE.GIF; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean compress(ImageCompressor imageCompressor) { |
||||||
|
return CompressTask.compress(imageCompressor, this, MAX_IMAGE_SIZE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param maxSize the proximate max size for compression |
||||||
|
* @return may be a little bigger than expected for performance. |
||||||
|
*/ |
||||||
|
public boolean compress(ImageCompressor imageCompressor, long maxSize) { |
||||||
|
return CompressTask.compress(imageCompressor, this, maxSize); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get mime type displayed in database. |
||||||
|
* |
||||||
|
* @return "image/gif" or "image/jpeg". |
||||||
|
*/ |
||||||
|
public String getMimeType() { |
||||||
|
if (getImageType() == IMAGE_TYPE.GIF) { |
||||||
|
return "image/gif"; |
||||||
|
} else if (getImageType() == IMAGE_TYPE.JPG) { |
||||||
|
return "image/jpeg"; |
||||||
|
} |
||||||
|
return "image/jpeg"; |
||||||
|
} |
||||||
|
|
||||||
|
public IMAGE_TYPE getImageType() { |
||||||
|
return mImageType; |
||||||
|
} |
||||||
|
|
||||||
|
private IMAGE_TYPE getImageTypeByMime(String mimeType) { |
||||||
|
if (!TextUtils.isEmpty(mimeType)) { |
||||||
|
if ("image/gif".equals(mimeType)) { |
||||||
|
return IMAGE_TYPE.GIF; |
||||||
|
} else if ("image/png".equals(mimeType)) { |
||||||
|
return IMAGE_TYPE.PNG; |
||||||
|
} else { |
||||||
|
return IMAGE_TYPE.JPG; |
||||||
|
} |
||||||
|
} |
||||||
|
return IMAGE_TYPE.PNG; |
||||||
|
} |
||||||
|
|
||||||
|
public void setImageType(IMAGE_TYPE imageType) { |
||||||
|
mImageType = imageType; |
||||||
|
} |
||||||
|
|
||||||
|
public String getId() { |
||||||
|
return mId; |
||||||
|
} |
||||||
|
|
||||||
|
public int getHeight() { |
||||||
|
return mHeight; |
||||||
|
} |
||||||
|
|
||||||
|
public int getWidth() { |
||||||
|
return mWidth; |
||||||
|
} |
||||||
|
|
||||||
|
public String getCompressPath() { |
||||||
|
return mCompressPath; |
||||||
|
} |
||||||
|
|
||||||
|
public void removeExif() { |
||||||
|
BoxingExifHelper.removeExif(getPath()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* save image to MediaStore. |
||||||
|
*/ |
||||||
|
public void saveMediaStore(final ContentResolver cr) { |
||||||
|
BoxingExecutor.getInstance().runWorker(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
if (cr != null && !TextUtils.isEmpty(getId())) { |
||||||
|
ContentValues values = new ContentValues(); |
||||||
|
values.put(MediaStore.Images.Media.TITLE, getId()); |
||||||
|
values.put(MediaStore.Images.Media.MIME_TYPE, getMimeType()); |
||||||
|
values.put(MediaStore.Images.Media.DATA, getPath()); |
||||||
|
cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void setCompressPath(String compressPath) { |
||||||
|
mCompressPath = compressPath; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSize(String size) { |
||||||
|
mSize = size; |
||||||
|
} |
||||||
|
|
||||||
|
public void setHeight(int height) { |
||||||
|
mHeight = height; |
||||||
|
} |
||||||
|
|
||||||
|
public void setWidth(int width) { |
||||||
|
mWidth = width; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "ImageMedia{" + |
||||||
|
", mThumbnailPath='" + mThumbnailPath + '\'' + |
||||||
|
", mCompressPath='" + mCompressPath + '\'' + |
||||||
|
", mSize='" + mSize + '\'' + |
||||||
|
", mHeight=" + mHeight + |
||||||
|
", mWidth=" + mWidth; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
int result = mId.hashCode(); |
||||||
|
result = 31 * result + (mPath != null ? mPath.hashCode() : 0); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
public String getThumbnailPath() { |
||||||
|
if (BoxingFileHelper.isFileValid(mThumbnailPath)) { |
||||||
|
return mThumbnailPath; |
||||||
|
} else if (BoxingFileHelper.isFileValid(mCompressPath)) { |
||||||
|
return mCompressPath; |
||||||
|
} |
||||||
|
return mPath; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if (this == obj) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (obj == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (getClass() != obj.getClass()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
final ImageMedia other = (ImageMedia) obj; |
||||||
|
return !(TextUtils.isEmpty(mPath) || TextUtils.isEmpty(other.mPath)) && this.mPath.equals(other.mPath); |
||||||
|
} |
||||||
|
|
||||||
|
public static class Builder { |
||||||
|
private String mId; |
||||||
|
private String mImagePath; |
||||||
|
private boolean mIsSelected; |
||||||
|
private String mThumbnailPath; |
||||||
|
private String mSize; |
||||||
|
private int mHeight; |
||||||
|
private int mWidth; |
||||||
|
private String mMimeType; |
||||||
|
|
||||||
|
public Builder(String id, String path) { |
||||||
|
this.mId = id; |
||||||
|
this.mImagePath = path; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setSelected(boolean selected) { |
||||||
|
this.mIsSelected = selected; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setThumbnailPath(String thumbnailPath) { |
||||||
|
mThumbnailPath = thumbnailPath; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setHeight(int height) { |
||||||
|
mHeight = height; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setWidth(int width) { |
||||||
|
mWidth = width; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setMimeType(String mimeType) { |
||||||
|
mMimeType = mimeType; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setSize(String size) { |
||||||
|
this.mSize = size; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public ImageMedia build() { |
||||||
|
return new ImageMedia(this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
super.writeToParcel(dest, flags); |
||||||
|
dest.writeByte(this.mIsSelected ? (byte) 1 : (byte) 0); |
||||||
|
dest.writeString(this.mThumbnailPath); |
||||||
|
dest.writeString(this.mCompressPath); |
||||||
|
dest.writeInt(this.mHeight); |
||||||
|
dest.writeInt(this.mWidth); |
||||||
|
dest.writeInt(this.mImageType == null ? -1 : this.mImageType.ordinal()); |
||||||
|
dest.writeString(this.mMimeType); |
||||||
|
} |
||||||
|
|
||||||
|
protected ImageMedia(Parcel in) { |
||||||
|
super(in); |
||||||
|
this.mIsSelected = in.readByte() != 0; |
||||||
|
this.mThumbnailPath = in.readString(); |
||||||
|
this.mCompressPath = in.readString(); |
||||||
|
this.mHeight = in.readInt(); |
||||||
|
this.mWidth = in.readInt(); |
||||||
|
int tmpMImageType = in.readInt(); |
||||||
|
this.mImageType = tmpMImageType == -1 ? null : IMAGE_TYPE.values()[tmpMImageType]; |
||||||
|
this.mMimeType = in.readString(); |
||||||
|
} |
||||||
|
|
||||||
|
public static final Creator<ImageMedia> CREATOR = new Creator<ImageMedia>() { |
||||||
|
@Override |
||||||
|
public ImageMedia createFromParcel(Parcel source) { |
||||||
|
return new ImageMedia(source); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ImageMedia[] newArray(int size) { |
||||||
|
return new ImageMedia[size]; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,196 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.entity.impl; |
||||||
|
|
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
|
||||||
|
import java.util.Locale; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Entity represent a Video. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class VideoMedia extends BaseMedia { |
||||||
|
private static final long MB = 1024 * 1024; |
||||||
|
|
||||||
|
private String mTitle; |
||||||
|
private String mDuration; |
||||||
|
private String mDateTaken; |
||||||
|
private String mMimeType; |
||||||
|
|
||||||
|
private VideoMedia() { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public TYPE getType() { |
||||||
|
return TYPE.VIDEO; |
||||||
|
} |
||||||
|
|
||||||
|
public VideoMedia(Builder builder) { |
||||||
|
super(builder.mId, builder.mPath); |
||||||
|
this.mTitle = builder.mTitle; |
||||||
|
this.mDuration = builder.mDuration; |
||||||
|
this.mSize = builder.mSize; |
||||||
|
this.mDateTaken = builder.mDateTaken; |
||||||
|
this.mMimeType = builder.mMimeType; |
||||||
|
} |
||||||
|
|
||||||
|
public String getDuration() { |
||||||
|
try { |
||||||
|
long duration = Long.parseLong(mDuration); |
||||||
|
return formatTimeWithMin(duration); |
||||||
|
} catch (NumberFormatException e) { |
||||||
|
return "0:00"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public String formatTimeWithMin(long duration) { |
||||||
|
if (duration <= 0) { |
||||||
|
return String.format(Locale.US, "%02d:%02d", 0, 0); |
||||||
|
} |
||||||
|
long totalSeconds = duration / 1000; |
||||||
|
|
||||||
|
long seconds = totalSeconds % 60; |
||||||
|
long minutes = (totalSeconds / 60) % 60; |
||||||
|
long hours = totalSeconds / 3600; |
||||||
|
|
||||||
|
if (hours > 0) { |
||||||
|
return String.format(Locale.US, "%02d:%02d", hours * 60 + minutes, |
||||||
|
seconds); |
||||||
|
} else { |
||||||
|
return String.format(Locale.US, "%02d:%02d", minutes, seconds); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setTitle(String title) { |
||||||
|
mTitle = title; |
||||||
|
} |
||||||
|
|
||||||
|
public void setDuration(String duration) { |
||||||
|
mDuration = duration; |
||||||
|
} |
||||||
|
|
||||||
|
public String getTitle() { |
||||||
|
return mTitle; |
||||||
|
} |
||||||
|
|
||||||
|
public String getSizeByUnit() { |
||||||
|
double size = getSize(); |
||||||
|
if (size == 0) { |
||||||
|
return "0K"; |
||||||
|
} |
||||||
|
if (size >= MB) { |
||||||
|
double sizeInM = size / MB; |
||||||
|
return String.format(Locale.getDefault(), "%.1f", sizeInM) + "M"; |
||||||
|
} |
||||||
|
double sizeInK = size / 1024; |
||||||
|
return String.format(Locale.getDefault(), "%.1f", sizeInK) + "K"; |
||||||
|
} |
||||||
|
|
||||||
|
public String getDateTaken() { |
||||||
|
return mDateTaken; |
||||||
|
} |
||||||
|
|
||||||
|
public String getMimeType() { |
||||||
|
return mMimeType; |
||||||
|
} |
||||||
|
|
||||||
|
public static class Builder { |
||||||
|
private String mId; |
||||||
|
private String mTitle; |
||||||
|
private String mPath; |
||||||
|
private String mDuration; |
||||||
|
private String mSize; |
||||||
|
private String mDateTaken; |
||||||
|
private String mMimeType; |
||||||
|
|
||||||
|
public Builder(String id, String path) { |
||||||
|
this.mId = id; |
||||||
|
this.mPath = path; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setTitle(String title) { |
||||||
|
this.mTitle = title; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setDuration(String duration) { |
||||||
|
this.mDuration = duration; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setSize(String size) { |
||||||
|
this.mSize = size; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setDataTaken(String dateTaken) { |
||||||
|
this.mDateTaken = dateTaken; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder setMimeType(String type) { |
||||||
|
this.mMimeType = type; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public VideoMedia build() { |
||||||
|
return new VideoMedia(this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
super.writeToParcel(dest, flags); |
||||||
|
dest.writeString(this.mTitle); |
||||||
|
dest.writeString(this.mDuration); |
||||||
|
dest.writeString(this.mDateTaken); |
||||||
|
dest.writeString(this.mMimeType); |
||||||
|
} |
||||||
|
|
||||||
|
protected VideoMedia(Parcel in) { |
||||||
|
super(in); |
||||||
|
this.mTitle = in.readString(); |
||||||
|
this.mDuration = in.readString(); |
||||||
|
this.mDateTaken = in.readString(); |
||||||
|
this.mMimeType = in.readString(); |
||||||
|
} |
||||||
|
|
||||||
|
public static final Parcelable.Creator<VideoMedia> CREATOR = new Parcelable.Creator<VideoMedia>() { |
||||||
|
@Override |
||||||
|
public VideoMedia createFromParcel(Parcel source) { |
||||||
|
return new VideoMedia(source); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public VideoMedia[] newArray(int size) { |
||||||
|
return new VideoMedia[size]; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.task; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.callback.IMediaTaskCallback; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* The interface to load {@link BaseMedia}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public interface IMediaTask<T extends BaseMedia> { |
||||||
|
int PAGE_LIMIT = 1000; |
||||||
|
|
||||||
|
void load(ContentResolver cr, int page, String id, IMediaTaskCallback<T> callback); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,187 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.task.impl; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.database.Cursor; |
||||||
|
import android.provider.MediaStore.Images.Media; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.callback.IAlbumTaskCallback; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.utils.BoxingExecutor; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.WorkerThread; |
||||||
|
import androidx.collection.ArrayMap; |
||||||
|
|
||||||
|
/** |
||||||
|
* A task to load albums. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
@WorkerThread |
||||||
|
public class AlbumTask { |
||||||
|
private static final String UNKNOWN_ALBUM_NAME = "unknow"; |
||||||
|
private static final String SELECTION_IMAGE_MIME_TYPE = Media.MIME_TYPE + "=? or " + Media.MIME_TYPE + "=? or " + Media.MIME_TYPE + "=? or " + Media.MIME_TYPE + "=?"; |
||||||
|
private static final String SELECTION_IMAGE_MIME_TYPE_WITHOUT_GIF = Media.MIME_TYPE + "=? or " + Media.MIME_TYPE + "=? or " + Media.MIME_TYPE + "=?"; |
||||||
|
private static final String SELECTION_ID = Media.BUCKET_ID + "=? and (" + SELECTION_IMAGE_MIME_TYPE + " )"; |
||||||
|
private static final String SELECTION_ID_WITHOUT_GIF = Media.BUCKET_ID + "=? and (" + SELECTION_IMAGE_MIME_TYPE_WITHOUT_GIF + " )"; |
||||||
|
private static final String[] SELECTION_ARGS_IMAGE_MIME_TYPE = {"image/jpeg", "image/png", "image/jpg", "image/gif"}; |
||||||
|
private static final String[] SELECTION_ARGS_IMAGE_MIME_TYPE_WITHOUT_GIF = {"image/jpeg", "image/png", "image/jpg"}; |
||||||
|
private int mUnknownAlbumNumber = 1; |
||||||
|
private Map<String, AlbumEntity> mBucketMap; |
||||||
|
private AlbumEntity mDefaultAlbum; |
||||||
|
private BoxingConfig mPickerConfig; |
||||||
|
|
||||||
|
public AlbumTask() { |
||||||
|
this.mBucketMap = new ArrayMap<>(); |
||||||
|
this.mDefaultAlbum = AlbumEntity.createDefaultAlbum(); |
||||||
|
this.mPickerConfig = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
} |
||||||
|
|
||||||
|
public void start(@NonNull final ContentResolver cr, @NonNull final IAlbumTaskCallback callback) { |
||||||
|
buildAlbumInfo(cr); |
||||||
|
getAlbumList(callback); |
||||||
|
} |
||||||
|
|
||||||
|
private void buildAlbumInfo(ContentResolver cr) { |
||||||
|
String[] distinctBucketColumns = new String[]{Media.BUCKET_ID, Media.BUCKET_DISPLAY_NAME}; |
||||||
|
Cursor bucketCursor = null; |
||||||
|
try { |
||||||
|
bucketCursor = cr.query(Media.EXTERNAL_CONTENT_URI, distinctBucketColumns, "0==0)" + " GROUP BY (" + Media.BUCKET_ID, null, |
||||||
|
Media.DATE_MODIFIED + " desc"); |
||||||
|
if (bucketCursor != null && bucketCursor.moveToFirst()) { |
||||||
|
do { |
||||||
|
String buckId = bucketCursor.getString(bucketCursor.getColumnIndex(Media.BUCKET_ID)); |
||||||
|
String name = bucketCursor.getString(bucketCursor.getColumnIndex(Media.BUCKET_DISPLAY_NAME)); |
||||||
|
AlbumEntity album = buildAlbumInfo(name, buckId); |
||||||
|
if (!TextUtils.isEmpty(buckId)) { |
||||||
|
buildAlbumCover(cr, buckId, album); |
||||||
|
} |
||||||
|
} while (bucketCursor.moveToNext()); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
if (bucketCursor != null) { |
||||||
|
bucketCursor.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get the cover and count |
||||||
|
* |
||||||
|
* @param buckId album id |
||||||
|
*/ |
||||||
|
private void buildAlbumCover(ContentResolver cr, String buckId, AlbumEntity album) { |
||||||
|
String[] photoColumn = new String[]{Media._ID, Media.DATA}; |
||||||
|
boolean isNeedGif = mPickerConfig != null && mPickerConfig.isNeedGif(); |
||||||
|
String selectionId = isNeedGif ? SELECTION_ID : SELECTION_ID_WITHOUT_GIF; |
||||||
|
String[] args = isNeedGif ? SELECTION_ARGS_IMAGE_MIME_TYPE : SELECTION_ARGS_IMAGE_MIME_TYPE_WITHOUT_GIF; |
||||||
|
String[] selectionArgs = new String[args.length + 1]; |
||||||
|
selectionArgs[0] = buckId; |
||||||
|
for (int i = 1; i < selectionArgs.length; i++) { |
||||||
|
selectionArgs[i] = args[i-1]; |
||||||
|
} |
||||||
|
Cursor coverCursor = cr.query(Media.EXTERNAL_CONTENT_URI, photoColumn, selectionId, |
||||||
|
selectionArgs, Media.DATE_MODIFIED + " desc"); |
||||||
|
try { |
||||||
|
if (coverCursor != null && coverCursor.moveToFirst()) { |
||||||
|
String picPath = coverCursor.getString(coverCursor.getColumnIndex(Media.DATA)); |
||||||
|
String id = coverCursor.getString(coverCursor.getColumnIndex(Media._ID)); |
||||||
|
album.mCount = coverCursor.getCount(); |
||||||
|
album.mImageList.add(new ImageMedia(id, picPath)); |
||||||
|
if (album.mImageList.size() > 0) { |
||||||
|
mBucketMap.put(buckId, album); |
||||||
|
} |
||||||
|
} |
||||||
|
} finally { |
||||||
|
if (coverCursor != null) { |
||||||
|
coverCursor.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void getAlbumList(@NonNull final IAlbumTaskCallback callback) { |
||||||
|
mDefaultAlbum.mCount = 0; |
||||||
|
List<AlbumEntity> tmpList = new ArrayList<>(); |
||||||
|
if (mBucketMap == null) { |
||||||
|
postAlbums(callback, tmpList); |
||||||
|
return; |
||||||
|
} |
||||||
|
for (Map.Entry<String, AlbumEntity> entry : mBucketMap.entrySet()) { |
||||||
|
tmpList.add(entry.getValue()); |
||||||
|
mDefaultAlbum.mCount += entry.getValue().mCount; |
||||||
|
} |
||||||
|
if (tmpList.size() > 0 && tmpList.get(0) != null) { |
||||||
|
mDefaultAlbum.mImageList = tmpList.get(0).mImageList; |
||||||
|
tmpList.add(0, mDefaultAlbum); |
||||||
|
} |
||||||
|
postAlbums(callback, tmpList); |
||||||
|
clear(); |
||||||
|
} |
||||||
|
|
||||||
|
private void postAlbums(@NonNull final IAlbumTaskCallback callback, final List<AlbumEntity> result) { |
||||||
|
BoxingExecutor.getInstance().runUI(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
callback.postAlbumList(result); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private AlbumEntity buildAlbumInfo(String bucketName, String bucketId) { |
||||||
|
AlbumEntity album = null; |
||||||
|
if (!TextUtils.isEmpty(bucketId)) { |
||||||
|
album = mBucketMap.get(bucketId); |
||||||
|
} |
||||||
|
if (album == null) { |
||||||
|
album = new AlbumEntity(); |
||||||
|
if (!TextUtils.isEmpty(bucketId)) { |
||||||
|
album.mBucketId = bucketId; |
||||||
|
} else { |
||||||
|
album.mBucketId = String.valueOf(mUnknownAlbumNumber); |
||||||
|
mUnknownAlbumNumber++; |
||||||
|
} |
||||||
|
if (!TextUtils.isEmpty(bucketName)) { |
||||||
|
album.mBucketName = bucketName; |
||||||
|
} else { |
||||||
|
album.mBucketName = UNKNOWN_ALBUM_NAME; |
||||||
|
mUnknownAlbumNumber++; |
||||||
|
} |
||||||
|
if (album.mImageList.size() > 0) { |
||||||
|
mBucketMap.put(bucketId, album); |
||||||
|
} |
||||||
|
} |
||||||
|
return album; |
||||||
|
} |
||||||
|
|
||||||
|
private void clear() { |
||||||
|
if (mBucketMap != null) { |
||||||
|
mBucketMap.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,231 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.task.impl; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.database.Cursor; |
||||||
|
import android.os.Build; |
||||||
|
import android.provider.MediaStore.Images; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.callback.IMediaTaskCallback; |
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.model.task.IMediaTask; |
||||||
|
import com.bilibili.boxing.utils.BoxingExecutor; |
||||||
|
import com.bilibili.boxing.utils.BoxingLog; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.WorkerThread; |
||||||
|
import androidx.collection.ArrayMap; |
||||||
|
|
||||||
|
/** |
||||||
|
* A Task to load photos. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
@WorkerThread |
||||||
|
public class ImageTask implements IMediaTask<ImageMedia> { |
||||||
|
private static final String CONJUNCTION_SQL = "=? or"; |
||||||
|
private static final String SELECTION_IMAGE_MIME_TYPE = Images.Media.MIME_TYPE + CONJUNCTION_SQL + " " + Images.Media.MIME_TYPE + CONJUNCTION_SQL + " " + Images.Media.MIME_TYPE + CONJUNCTION_SQL + " " + Images.Media.MIME_TYPE + "=?"; |
||||||
|
private static final String SELECTION_IMAGE_MIME_TYPE_WITHOUT_GIF = Images.Media.MIME_TYPE + CONJUNCTION_SQL + " " + Images.Media.MIME_TYPE + CONJUNCTION_SQL + " " + Images.Media.MIME_TYPE + "=?"; |
||||||
|
private static final String SELECTION_ID = Images.Media.BUCKET_ID + "=? and (" + SELECTION_IMAGE_MIME_TYPE + " )"; |
||||||
|
private static final String SELECTION_ID_WITHOUT_GIF = Images.Media.BUCKET_ID + "=? and (" + SELECTION_IMAGE_MIME_TYPE_WITHOUT_GIF + " )"; |
||||||
|
|
||||||
|
private static final String IMAGE_JPEG = "image/jpeg"; |
||||||
|
private static final String IMAGE_PNG = "image/png"; |
||||||
|
private static final String IMAGE_JPG = "image/jpg"; |
||||||
|
private static final String IMAGE_GIF = "image/gif"; |
||||||
|
private static final String[] SELECTION_ARGS_IMAGE_MIME_TYPE = {IMAGE_JPEG, IMAGE_PNG, IMAGE_JPG, IMAGE_GIF}; |
||||||
|
private static final String[] SELECTION_ARGS_IMAGE_MIME_TYPE_WITHOUT_GIF = {IMAGE_JPEG, IMAGE_PNG, IMAGE_JPG}; |
||||||
|
|
||||||
|
private static final String DESC = " desc"; |
||||||
|
|
||||||
|
private BoxingConfig mPickerConfig; |
||||||
|
private Map<String, String> mThumbnailMap; |
||||||
|
|
||||||
|
public ImageTask() { |
||||||
|
this.mThumbnailMap = new ArrayMap<>(); |
||||||
|
this.mPickerConfig = BoxingManager.getInstance().getBoxingConfig(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void load(@NonNull final ContentResolver cr, final int page, final String id, |
||||||
|
@NonNull final IMediaTaskCallback<ImageMedia> callback) { |
||||||
|
buildThumbnail(cr); |
||||||
|
buildAlbumList(cr, id, page, callback); |
||||||
|
} |
||||||
|
|
||||||
|
private void buildThumbnail(ContentResolver cr) { |
||||||
|
String[] projection = {Images.Thumbnails.IMAGE_ID, Images.Thumbnails.DATA}; |
||||||
|
queryThumbnails(cr, projection); |
||||||
|
} |
||||||
|
|
||||||
|
private void queryThumbnails(ContentResolver cr, String[] projection) { |
||||||
|
Cursor cur = null; |
||||||
|
try { |
||||||
|
cur = Images.Thumbnails.queryMiniThumbnails(cr, Images.Thumbnails.EXTERNAL_CONTENT_URI, |
||||||
|
Images.Thumbnails.MINI_KIND, projection); |
||||||
|
if (cur != null && cur.moveToFirst()) { |
||||||
|
do { |
||||||
|
String imageId = cur.getString(cur.getColumnIndex(Images.Thumbnails.IMAGE_ID)); |
||||||
|
String imagePath = cur.getString(cur.getColumnIndex(Images.Thumbnails.DATA)); |
||||||
|
mThumbnailMap.put(imageId, imagePath); |
||||||
|
} while (cur.moveToNext() && !cur.isLast()); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
if (cur != null) { |
||||||
|
cur.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private List<ImageMedia> buildAlbumList(ContentResolver cr, String bucketId, int page, |
||||||
|
@NonNull final IMediaTaskCallback<ImageMedia> callback) { |
||||||
|
List<ImageMedia> result = new ArrayList<>(); |
||||||
|
String columns[] = getColumns(); |
||||||
|
Cursor cursor = null; |
||||||
|
try { |
||||||
|
boolean isDefaultAlbum = TextUtils.isEmpty(bucketId); |
||||||
|
boolean isNeedPaging = mPickerConfig == null || mPickerConfig.isNeedPaging(); |
||||||
|
boolean isNeedGif = mPickerConfig != null && mPickerConfig.isNeedGif(); |
||||||
|
int totalCount = getTotalCount(cr, bucketId, columns, isDefaultAlbum, isNeedGif); |
||||||
|
|
||||||
|
String imageMimeType = isNeedGif ? SELECTION_IMAGE_MIME_TYPE : SELECTION_IMAGE_MIME_TYPE_WITHOUT_GIF; |
||||||
|
String[] args = isNeedGif ? SELECTION_ARGS_IMAGE_MIME_TYPE : SELECTION_ARGS_IMAGE_MIME_TYPE_WITHOUT_GIF; |
||||||
|
String order = isNeedPaging ? Images.Media.DATE_MODIFIED + DESC + " LIMIT " |
||||||
|
+ page * IMediaTask.PAGE_LIMIT + " , " + IMediaTask.PAGE_LIMIT : Images.Media.DATE_MODIFIED + DESC; |
||||||
|
String selectionId = isNeedGif ? SELECTION_ID : SELECTION_ID_WITHOUT_GIF; |
||||||
|
cursor = query(cr, bucketId, columns, isDefaultAlbum, isNeedGif, imageMimeType, args, order, selectionId); |
||||||
|
addItem(totalCount, result, cursor, callback); |
||||||
|
} finally { |
||||||
|
if (cursor != null) { |
||||||
|
cursor.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
private void addItem(final int allCount, final List<ImageMedia> result, Cursor cursor, @NonNull final IMediaTaskCallback<ImageMedia> callback) { |
||||||
|
if (cursor != null && cursor.moveToFirst()) { |
||||||
|
do { |
||||||
|
String picPath = cursor.getString(cursor.getColumnIndex(Images.Media.DATA)); |
||||||
|
if (callback.needFilter(picPath)) { |
||||||
|
BoxingLog.d("path:" + picPath + " has been filter"); |
||||||
|
} else { |
||||||
|
String id = cursor.getString(cursor.getColumnIndex(Images.Media._ID)); |
||||||
|
String size = cursor.getString(cursor.getColumnIndex(Images.Media.SIZE)); |
||||||
|
String mimeType = cursor.getString(cursor.getColumnIndex(Images.Media.MIME_TYPE)); |
||||||
|
int width = 0; |
||||||
|
int height = 0; |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
||||||
|
width = cursor.getInt(cursor.getColumnIndex(Images.Media.WIDTH)); |
||||||
|
height = cursor.getInt(cursor.getColumnIndex(Images.Media.HEIGHT)); |
||||||
|
} |
||||||
|
ImageMedia imageItem = new ImageMedia.Builder(id, picPath).setThumbnailPath(mThumbnailMap.get(id)) |
||||||
|
.setSize(size).setMimeType(mimeType).setHeight(height).setWidth(width).build(); |
||||||
|
if (!result.contains(imageItem)) { |
||||||
|
result.add(imageItem); |
||||||
|
} |
||||||
|
} |
||||||
|
} while (!cursor.isLast() && cursor.moveToNext()); |
||||||
|
postMedias(result, allCount, callback); |
||||||
|
} else { |
||||||
|
postMedias(result, 0, callback); |
||||||
|
} |
||||||
|
clear(); |
||||||
|
} |
||||||
|
|
||||||
|
private void postMedias(final List<ImageMedia> result, final int count, @NonNull final IMediaTaskCallback<ImageMedia> callback) { |
||||||
|
BoxingExecutor.getInstance().runUI(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
callback.postMedia(result, count); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private Cursor query(ContentResolver cr, String bucketId, String[] columns, boolean isDefaultAlbum, |
||||||
|
boolean isNeedGif, String imageMimeType, String[] args, String order, String selectionId) { |
||||||
|
Cursor resultCursor; |
||||||
|
if (isDefaultAlbum) { |
||||||
|
resultCursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, columns, imageMimeType, |
||||||
|
args, order); |
||||||
|
} else { |
||||||
|
if (isNeedGif) { |
||||||
|
resultCursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, columns, selectionId, |
||||||
|
new String[]{bucketId, args[0], args[1], args[2], args[3]}, order); |
||||||
|
} else { |
||||||
|
resultCursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, columns, selectionId, |
||||||
|
new String[]{bucketId, args[0], args[1], args[2]}, order); |
||||||
|
} |
||||||
|
} |
||||||
|
return resultCursor; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private String[] getColumns() { |
||||||
|
String[] columns; |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
||||||
|
columns = new String[]{Images.Media._ID, Images.Media.DATA, Images.Media.SIZE, Images.Media.MIME_TYPE, Images.Media.WIDTH, Images.Media.HEIGHT}; |
||||||
|
} else { |
||||||
|
columns = new String[]{Images.Media._ID, Images.Media.DATA, Images.Media.SIZE, Images.Media.MIME_TYPE}; |
||||||
|
} |
||||||
|
return columns; |
||||||
|
} |
||||||
|
|
||||||
|
private int getTotalCount(ContentResolver cr, String bucketId, String[] columns, boolean isDefaultAlbum, boolean isNeedGif) { |
||||||
|
Cursor allCursor = null; |
||||||
|
int result = 0; |
||||||
|
try { |
||||||
|
if (isDefaultAlbum) { |
||||||
|
allCursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, columns, |
||||||
|
SELECTION_IMAGE_MIME_TYPE, SELECTION_ARGS_IMAGE_MIME_TYPE, |
||||||
|
Images.Media.DATE_MODIFIED + DESC); |
||||||
|
} else { |
||||||
|
if (isNeedGif) { |
||||||
|
allCursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, columns, SELECTION_ID, |
||||||
|
new String[]{bucketId, IMAGE_JPEG, IMAGE_PNG, IMAGE_JPG, IMAGE_GIF}, Images.Media.DATE_MODIFIED + DESC); |
||||||
|
} else { |
||||||
|
allCursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, columns, SELECTION_ID_WITHOUT_GIF, |
||||||
|
new String[]{bucketId, IMAGE_JPEG, IMAGE_PNG, IMAGE_JPG}, Images.Media.DATE_MODIFIED + DESC); |
||||||
|
} |
||||||
|
} |
||||||
|
if (allCursor != null) { |
||||||
|
result = allCursor.getCount(); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
if (allCursor != null) { |
||||||
|
allCursor.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
private void clear() { |
||||||
|
if (mThumbnailMap != null) { |
||||||
|
mThumbnailMap.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,102 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.model.task.impl; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.database.Cursor; |
||||||
|
import android.provider.MediaStore; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.callback.IMediaTaskCallback; |
||||||
|
import com.bilibili.boxing.model.entity.impl.VideoMedia; |
||||||
|
import com.bilibili.boxing.model.task.IMediaTask; |
||||||
|
import com.bilibili.boxing.utils.BoxingExecutor; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.WorkerThread; |
||||||
|
|
||||||
|
/** |
||||||
|
* A Task to load {@link VideoMedia} in database. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
@WorkerThread |
||||||
|
public class VideoTask implements IMediaTask<VideoMedia> { |
||||||
|
|
||||||
|
private final static String[] MEDIA_COL = new String[]{ |
||||||
|
MediaStore.Video.Media.DATA, |
||||||
|
MediaStore.Video.Media._ID, |
||||||
|
MediaStore.Video.Media.TITLE, |
||||||
|
MediaStore.Video.Media.MIME_TYPE, |
||||||
|
MediaStore.Video.Media.SIZE, |
||||||
|
MediaStore.Video.Media.DATE_TAKEN, |
||||||
|
MediaStore.Video.Media.DURATION |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void load(final ContentResolver cr, final int page, String id, final IMediaTaskCallback<VideoMedia> callback) { |
||||||
|
loadVideos(cr, page, callback); |
||||||
|
} |
||||||
|
|
||||||
|
private void loadVideos(ContentResolver cr, int page, @NonNull final IMediaTaskCallback<VideoMedia> callback) { |
||||||
|
final List<VideoMedia> videoMedias = new ArrayList<>(); |
||||||
|
final Cursor cursor = cr.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, MEDIA_COL, null, null, |
||||||
|
MediaStore.Images.Media.DATE_MODIFIED + " desc" + " LIMIT " + page * IMediaTask.PAGE_LIMIT + " , " + IMediaTask.PAGE_LIMIT); |
||||||
|
try { |
||||||
|
int count = 0; |
||||||
|
if (cursor != null && cursor.moveToFirst()) { |
||||||
|
count = cursor.getCount(); |
||||||
|
do { |
||||||
|
String data = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); |
||||||
|
String id = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media._ID)); |
||||||
|
String title = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.TITLE)); |
||||||
|
String type = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.MIME_TYPE)); |
||||||
|
String size = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.SIZE)); |
||||||
|
String date = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATE_TAKEN)); |
||||||
|
String duration = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DURATION)); |
||||||
|
VideoMedia video = new VideoMedia.Builder(id, data).setTitle(title).setDuration(duration) |
||||||
|
.setSize(size).setDataTaken(date).setMimeType(type).build(); |
||||||
|
videoMedias.add(video); |
||||||
|
|
||||||
|
} while (!cursor.isLast() && cursor.moveToNext()); |
||||||
|
postMedias(callback, videoMedias, count); |
||||||
|
} else { |
||||||
|
postMedias(callback, videoMedias, 0); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
if (cursor != null) { |
||||||
|
cursor.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private void postMedias(@NonNull final IMediaTaskCallback<VideoMedia> callback, |
||||||
|
final List<VideoMedia> videoMedias, final int count) { |
||||||
|
BoxingExecutor.getInstance().runUI(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
callback.postMedia(videoMedias, count); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.presenter; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.config.BoxingConfig; |
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* This specifies the contract between the view and the presenter. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public interface PickerContract { |
||||||
|
|
||||||
|
/** |
||||||
|
* define the functions of the view, interacting with presenter |
||||||
|
*/ |
||||||
|
interface View { |
||||||
|
/** |
||||||
|
* set the presenter attaching to the view |
||||||
|
*/ |
||||||
|
void setPresenter(@NonNull Presenter presenter); |
||||||
|
|
||||||
|
/** |
||||||
|
* show a list the {@link BaseMedia} in the view |
||||||
|
*/ |
||||||
|
void showMedia(@Nullable List<BaseMedia> medias, int allCount); |
||||||
|
|
||||||
|
/** |
||||||
|
* show all the {@link AlbumEntity} in the view |
||||||
|
*/ |
||||||
|
void showAlbum(@Nullable List<AlbumEntity> albums); |
||||||
|
|
||||||
|
/** |
||||||
|
* get the {@link ContentResolver} in the view |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
ContentResolver getAppCr(); |
||||||
|
|
||||||
|
/** |
||||||
|
* call when the view should be finished or the process is finished |
||||||
|
* |
||||||
|
* @param medias the selection of medias. |
||||||
|
*/ |
||||||
|
void onFinish(@NonNull List<BaseMedia> medias); |
||||||
|
|
||||||
|
/** |
||||||
|
* clear all the {@link BaseMedia} in the view |
||||||
|
*/ |
||||||
|
void clearMedia(); |
||||||
|
|
||||||
|
/** |
||||||
|
* start crop the {@link BaseMedia} in the single media mode |
||||||
|
*/ |
||||||
|
void startCrop(@NonNull BaseMedia media, int requestCode); |
||||||
|
|
||||||
|
/** |
||||||
|
* set or update the config. |
||||||
|
* |
||||||
|
* @param config {@link BoxingConfig} |
||||||
|
*/ |
||||||
|
void setPickerConfig(BoxingConfig config); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* define the function of presenter, to control the module to load data and to tell view to displayRaw the data |
||||||
|
*/ |
||||||
|
interface Presenter { |
||||||
|
/** |
||||||
|
* load the specify data from {@link ContentResolver} |
||||||
|
* |
||||||
|
* @param page the page need to load |
||||||
|
* @param albumId album albumId |
||||||
|
*/ |
||||||
|
void loadMedias(int page, String albumId); |
||||||
|
|
||||||
|
/** |
||||||
|
* load all the album from {@link ContentResolver} |
||||||
|
*/ |
||||||
|
void loadAlbums(); |
||||||
|
|
||||||
|
/** |
||||||
|
* destroy the presenter and set the view null |
||||||
|
*/ |
||||||
|
void destroy(); |
||||||
|
|
||||||
|
/** |
||||||
|
* has more data to load |
||||||
|
* |
||||||
|
* @return true, have more |
||||||
|
*/ |
||||||
|
boolean hasNextPage(); |
||||||
|
|
||||||
|
boolean canLoadNextPage(); |
||||||
|
|
||||||
|
/** |
||||||
|
* load next page |
||||||
|
*/ |
||||||
|
void onLoadNextPage(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Determine the selected allMedias according to mSelectedMedias |
||||||
|
* |
||||||
|
* @param allMedias all medias |
||||||
|
* @param selectedMedias the medias to be selected |
||||||
|
*/ |
||||||
|
void checkSelectedMedia(List<BaseMedia> allMedias, List<BaseMedia> selectedMedias); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,178 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.presenter; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingManager; |
||||||
|
import com.bilibili.boxing.model.callback.IAlbumTaskCallback; |
||||||
|
import com.bilibili.boxing.model.callback.IMediaTaskCallback; |
||||||
|
import com.bilibili.boxing.model.entity.AlbumEntity; |
||||||
|
import com.bilibili.boxing.model.entity.BaseMedia; |
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
import com.bilibili.boxing.model.task.IMediaTask; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.lang.ref.WeakReference; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A presenter implement {@link com.bilibili.boxing.presenter.PickerContract.Presenter}. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class PickerPresenter implements PickerContract.Presenter { |
||||||
|
private PickerContract.View mTasksView; |
||||||
|
|
||||||
|
private int mTotalPage; |
||||||
|
private int mCurrentPage; |
||||||
|
private boolean mIsLoadingNextPage; |
||||||
|
|
||||||
|
private String mCurrentAlbumId; |
||||||
|
private LoadMediaCallback mLoadMediaCallback; |
||||||
|
private LoadAlbumCallback mLoadAlbumCallback; |
||||||
|
|
||||||
|
public PickerPresenter(PickerContract.View tasksView) { |
||||||
|
this.mTasksView = tasksView; |
||||||
|
this.mTasksView.setPresenter(this); |
||||||
|
this.mLoadMediaCallback = new LoadMediaCallback(this); |
||||||
|
this.mLoadAlbumCallback = new LoadAlbumCallback(this); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void loadMedias(int page, String albumId) { |
||||||
|
mCurrentAlbumId = albumId; |
||||||
|
if (page == 0) { |
||||||
|
mTasksView.clearMedia(); |
||||||
|
mCurrentPage = 0; |
||||||
|
} |
||||||
|
ContentResolver cr = mTasksView.getAppCr(); |
||||||
|
BoxingManager.getInstance().loadMedia(cr, page, albumId, mLoadMediaCallback); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void loadAlbums() { |
||||||
|
ContentResolver cr = mTasksView.getAppCr(); |
||||||
|
BoxingManager.getInstance().loadAlbum(cr, mLoadAlbumCallback); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void destroy() { |
||||||
|
mTasksView = null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean hasNextPage() { |
||||||
|
return mCurrentPage < mTotalPage; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean canLoadNextPage() { |
||||||
|
return !mIsLoadingNextPage; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onLoadNextPage() { |
||||||
|
mCurrentPage++; |
||||||
|
mIsLoadingNextPage = true; |
||||||
|
loadMedias(mCurrentPage, mCurrentAlbumId); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void checkSelectedMedia(List<BaseMedia> allMedias, List<BaseMedia> selectedMedias) { |
||||||
|
if (allMedias == null || allMedias.size() == 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
Map<String, ImageMedia> map = new HashMap<>(allMedias.size()); |
||||||
|
for (BaseMedia allMedia : allMedias) { |
||||||
|
if (!(allMedia instanceof ImageMedia)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
ImageMedia media = (ImageMedia) allMedia; |
||||||
|
media.setSelected(false); |
||||||
|
map.put(media.getPath(), media); |
||||||
|
} |
||||||
|
if (selectedMedias == null || selectedMedias.size() < 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (BaseMedia media : selectedMedias) { |
||||||
|
if (map.containsKey(media.getPath())) { |
||||||
|
map.get(media.getPath()).setSelected(true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class LoadMediaCallback implements IMediaTaskCallback<BaseMedia> { |
||||||
|
private WeakReference<PickerPresenter> mWr; |
||||||
|
|
||||||
|
LoadMediaCallback(PickerPresenter presenter) { |
||||||
|
mWr = new WeakReference<>(presenter); |
||||||
|
} |
||||||
|
|
||||||
|
private PickerPresenter getPresenter() { |
||||||
|
return mWr.get(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void postMedia(List<BaseMedia> medias, int count) { |
||||||
|
PickerPresenter presenter = getPresenter(); |
||||||
|
if (presenter == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
PickerContract.View view = presenter.mTasksView; |
||||||
|
if (view != null) { |
||||||
|
view.showMedia(medias, count); |
||||||
|
} |
||||||
|
presenter.mTotalPage = count / IMediaTask.PAGE_LIMIT; |
||||||
|
presenter.mIsLoadingNextPage = false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean needFilter(String path) { |
||||||
|
return TextUtils.isEmpty(path) || !(new File(path).exists()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class LoadAlbumCallback implements IAlbumTaskCallback { |
||||||
|
private WeakReference<PickerPresenter> mWr; |
||||||
|
|
||||||
|
LoadAlbumCallback(PickerPresenter presenter) { |
||||||
|
mWr = new WeakReference<>(presenter); |
||||||
|
} |
||||||
|
|
||||||
|
private PickerPresenter getPresenter() { |
||||||
|
return mWr.get(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void postAlbumList(List<AlbumEntity> list) { |
||||||
|
PickerPresenter presenter = getPresenter(); |
||||||
|
if (presenter == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (presenter.mTasksView != null) { |
||||||
|
presenter.mTasksView.showAlbum(list); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.utils; |
||||||
|
|
||||||
|
import android.os.Handler; |
||||||
|
import android.os.Looper; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
|
||||||
|
import java.util.concurrent.Callable; |
||||||
|
import java.util.concurrent.ExecutorService; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
import java.util.concurrent.FutureTask; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingExecutor { |
||||||
|
private static final BoxingExecutor INSTANCE = new BoxingExecutor(); |
||||||
|
|
||||||
|
private ExecutorService mExecutorService; |
||||||
|
|
||||||
|
private BoxingExecutor() { |
||||||
|
} |
||||||
|
|
||||||
|
public static BoxingExecutor getInstance() { |
||||||
|
return INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public void runWorker(@NonNull Runnable runnable) { |
||||||
|
ensureWorkerHandlerNotNull(); |
||||||
|
try { |
||||||
|
mExecutorService.execute(runnable); |
||||||
|
} catch (Exception e) { |
||||||
|
BoxingLog.d("runnable stop running unexpected. " + e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
public FutureTask<Boolean> runWorker(@NonNull Callable<Boolean> callable) { |
||||||
|
ensureWorkerHandlerNotNull(); |
||||||
|
FutureTask<Boolean> task = null; |
||||||
|
try { |
||||||
|
task = new FutureTask<>(callable); |
||||||
|
mExecutorService.submit(task); |
||||||
|
return task; |
||||||
|
} catch (Exception e) { |
||||||
|
BoxingLog.d("callable stop running unexpected. " + e.getMessage()); |
||||||
|
} |
||||||
|
return task; |
||||||
|
} |
||||||
|
|
||||||
|
public void runUI(@NonNull Runnable runnable) { |
||||||
|
if (Looper.myLooper() == Looper.getMainLooper()) { |
||||||
|
runnable.run(); |
||||||
|
return; |
||||||
|
} |
||||||
|
Handler handler = ensureUiHandlerNotNull(); |
||||||
|
try { |
||||||
|
handler.post(runnable); |
||||||
|
} catch (Exception e) { |
||||||
|
BoxingLog.d("update UI task fail. " + e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void ensureWorkerHandlerNotNull() { |
||||||
|
if (mExecutorService == null) { |
||||||
|
mExecutorService = Executors.newCachedThreadPool(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Handler ensureUiHandlerNotNull() { |
||||||
|
return new Handler(Looper.getMainLooper()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.utils; |
||||||
|
|
||||||
|
import android.media.ExifInterface; |
||||||
|
import android.os.Build; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
|
||||||
|
public class BoxingExifHelper { |
||||||
|
|
||||||
|
public static void removeExif(String path) { |
||||||
|
if (!TextUtils.isEmpty(path)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
ExifInterface exifInterface; |
||||||
|
try { |
||||||
|
exifInterface = new ExifInterface(path); |
||||||
|
} catch (IOException ignore) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_ARTIST, ""); |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_RESOLUTION_UNIT, "0"); |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, ""); |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_MAKER_NOTE, "0"); |
||||||
|
} |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, ""); |
||||||
|
} |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_MAKE, ""); |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_MODEL, ""); |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, "0"); |
||||||
|
|
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_DATETIME, ""); |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE, ""); |
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, ""); |
||||||
|
|
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE, ""); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static int getRotateDegree(String path) { |
||||||
|
int result = 0; |
||||||
|
try { |
||||||
|
ExifInterface exif = new ExifInterface(path); |
||||||
|
int orientation = exif.getAttributeInt( |
||||||
|
ExifInterface.TAG_ORIENTATION, |
||||||
|
ExifInterface.ORIENTATION_NORMAL); |
||||||
|
switch (orientation) { |
||||||
|
case ExifInterface.ORIENTATION_ROTATE_90: |
||||||
|
result = 90; |
||||||
|
break; |
||||||
|
case ExifInterface.ORIENTATION_ROTATE_180: |
||||||
|
result = 180; |
||||||
|
break; |
||||||
|
case ExifInterface.ORIENTATION_ROTATE_270: |
||||||
|
result = 270; |
||||||
|
break; |
||||||
|
} |
||||||
|
} catch (IOException ignore) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,103 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.utils; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.os.Environment; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
|
||||||
|
/** |
||||||
|
* A file helper to make thing easier. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingFileHelper { |
||||||
|
public static final String DEFAULT_SUB_DIR = "/bili/boxing"; |
||||||
|
|
||||||
|
public static boolean createFile(String path) throws ExecutionException, InterruptedException { |
||||||
|
if (TextUtils.isEmpty(path)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
final File file = new File(path); |
||||||
|
return file.exists() || file.mkdirs(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
public static String getCacheDir(@NonNull Context context) { |
||||||
|
if (context == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
context = context.getApplicationContext(); |
||||||
|
File cacheDir = context.getCacheDir(); |
||||||
|
if (cacheDir == null) { |
||||||
|
BoxingLog.d("cache dir do not exist."); |
||||||
|
return null; |
||||||
|
} |
||||||
|
String result = cacheDir.getAbsolutePath() + "/boxing"; |
||||||
|
try { |
||||||
|
BoxingFileHelper.createFile(result); |
||||||
|
} catch (ExecutionException | InterruptedException e) { |
||||||
|
BoxingLog.d("cache dir " + result + " not exist"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
BoxingLog.d("cache dir is: " + result); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
public static String getBoxingPathInDCIM() { |
||||||
|
return getExternalDCIM(null); |
||||||
|
} |
||||||
|
|
||||||
|
@Nullable |
||||||
|
public static String getExternalDCIM(String subDir) { |
||||||
|
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { |
||||||
|
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); |
||||||
|
if (file == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
String dir = "/bili/boxing"; |
||||||
|
if (!TextUtils.isEmpty(subDir)) { |
||||||
|
dir = subDir; |
||||||
|
} |
||||||
|
String result = file.getAbsolutePath() + dir; |
||||||
|
BoxingLog.d("external DCIM is: " + result); |
||||||
|
return result; |
||||||
|
} |
||||||
|
BoxingLog.d("external DCIM do not exist."); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean isFileValid(String path) { |
||||||
|
if (TextUtils.isEmpty(path)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
File file = new File(path); |
||||||
|
return isFileValid(file); |
||||||
|
} |
||||||
|
|
||||||
|
static boolean isFileValid(File file) { |
||||||
|
return file != null && file.exists() && file.isFile() && file.length() > 0 && file.canRead(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.utils; |
||||||
|
|
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.BoxingBuilderConfig; |
||||||
|
|
||||||
|
/** |
||||||
|
* Debug log tool. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class BoxingLog { |
||||||
|
private static final String TAG = "com.bilibili.boxing"; |
||||||
|
|
||||||
|
public static void d(String log) { |
||||||
|
if (BoxingBuilderConfig.DEBUG && !BoxingBuilderConfig.TESTING) { |
||||||
|
Log.d(TAG, log); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,308 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.utils; |
||||||
|
|
||||||
|
import android.app.Activity; |
||||||
|
import android.content.ActivityNotFoundException; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.Intent; |
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.BitmapFactory; |
||||||
|
import android.graphics.Matrix; |
||||||
|
import android.hardware.Camera; |
||||||
|
import android.net.Uri; |
||||||
|
import android.os.Build; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
import android.provider.MediaStore; |
||||||
|
|
||||||
|
import com.bilibili.boxing.AbsBoxingViewFragment; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.concurrent.Callable; |
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
import java.util.concurrent.FutureTask; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.core.content.FileProvider; |
||||||
|
import androidx.fragment.app.Fragment; |
||||||
|
|
||||||
|
/** |
||||||
|
* A helper to start camera.<br/> |
||||||
|
* used by {@link AbsBoxingViewFragment} |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class CameraPickerHelper { |
||||||
|
private static final int MAX_CAMER_PHOTO_SIZE = 4 * 1024 * 1024; |
||||||
|
public static final int REQ_CODE_CAMERA = 0x2001; |
||||||
|
private static final String STATE_SAVED_KEY = "com.bilibili.boxing.utils.CameraPickerHelper.saved_state"; |
||||||
|
|
||||||
|
private String mSourceFilePath; |
||||||
|
private File mOutputFile; |
||||||
|
private Callback mCallback; |
||||||
|
|
||||||
|
public interface Callback { |
||||||
|
void onFinish(@NonNull CameraPickerHelper helper); |
||||||
|
|
||||||
|
void onError(@NonNull CameraPickerHelper helper); |
||||||
|
} |
||||||
|
|
||||||
|
public CameraPickerHelper(@Nullable Bundle savedInstance) { |
||||||
|
if (savedInstance != null) { |
||||||
|
SavedState state = savedInstance.getParcelable(STATE_SAVED_KEY); |
||||||
|
if (state != null) { |
||||||
|
mOutputFile = state.mOutputFile; |
||||||
|
mSourceFilePath = state.mSourceFilePath; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setPickCallback(Callback callback) { |
||||||
|
this.mCallback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void onSaveInstanceState(Bundle out) { |
||||||
|
SavedState state = new SavedState(); |
||||||
|
state.mOutputFile = mOutputFile; |
||||||
|
state.mSourceFilePath = mSourceFilePath; |
||||||
|
out.putParcelable(STATE_SAVED_KEY, state); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* start system camera to take a picture |
||||||
|
* |
||||||
|
* @param activity not null if fragment is null. |
||||||
|
* @param fragment not null if activity is null. |
||||||
|
* @param subFolderPath a folder in external DCIM,must start with "/". |
||||||
|
*/ |
||||||
|
public void startCamera(final Activity activity, final Fragment fragment, final String subFolderPath) { |
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !takePhotoSecure(activity, fragment, subFolderPath)) { |
||||||
|
FutureTask<Boolean> task = BoxingExecutor.getInstance().runWorker(new Callable<Boolean>() { |
||||||
|
@Override |
||||||
|
public Boolean call() throws Exception { |
||||||
|
try { |
||||||
|
// try...try...try
|
||||||
|
Camera camera = Camera.open(); |
||||||
|
camera.release(); |
||||||
|
} catch (Exception e) { |
||||||
|
BoxingLog.d("camera is not available."); |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
}); |
||||||
|
try { |
||||||
|
if (task != null && task.get()) { |
||||||
|
startCameraIntent(activity, fragment, subFolderPath, MediaStore.ACTION_IMAGE_CAPTURE, REQ_CODE_CAMERA); |
||||||
|
} else { |
||||||
|
callbackError(); |
||||||
|
} |
||||||
|
} catch (InterruptedException | ExecutionException ignore) { |
||||||
|
callbackError(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean takePhotoSecure(Activity activity, Fragment fragment, String subDir) { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
||||||
|
try { |
||||||
|
startCameraIntent(activity, fragment, subDir, MediaStore.ACTION_IMAGE_CAPTURE, REQ_CODE_CAMERA); |
||||||
|
return true; |
||||||
|
} catch (ActivityNotFoundException ignore) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private void callbackFinish() { |
||||||
|
if (mCallback != null) { |
||||||
|
mCallback.onFinish(CameraPickerHelper.this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void callbackError() { |
||||||
|
if (mCallback != null) { |
||||||
|
mCallback.onError(CameraPickerHelper.this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void startActivityForResult(Activity activity, Fragment fragment, final Intent intent, final int reqCodeCamera) throws ActivityNotFoundException { |
||||||
|
if (fragment == null) { |
||||||
|
activity.startActivityForResult(intent, reqCodeCamera); |
||||||
|
} else { |
||||||
|
fragment.startActivityForResult(intent, reqCodeCamera); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void startCameraIntent(final Activity activity, final Fragment fragment, String subFolder, |
||||||
|
final String action, final int requestCode) { |
||||||
|
final String cameraOutDir = BoxingFileHelper.getExternalDCIM(subFolder); |
||||||
|
try { |
||||||
|
if (BoxingFileHelper.createFile(cameraOutDir)) { |
||||||
|
mOutputFile = new File(cameraOutDir, System.currentTimeMillis() + ".jpg"); |
||||||
|
mSourceFilePath = mOutputFile.getPath(); |
||||||
|
Intent intent = new Intent(action); |
||||||
|
Uri uri = getFileUri(activity.getApplicationContext(), mOutputFile); |
||||||
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); |
||||||
|
try { |
||||||
|
startActivityForResult(activity, fragment, intent, requestCode); |
||||||
|
} catch (ActivityNotFoundException ignore) { |
||||||
|
callbackError(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} catch (ExecutionException | InterruptedException e) { |
||||||
|
BoxingLog.d("create file" + cameraOutDir + " error."); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private Uri getFileUri(@NonNull Context context, @NonNull File file) { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
||||||
|
return FileProvider.getUriForFile(context, |
||||||
|
context.getApplicationContext().getPackageName() + ".file.provider", mOutputFile); |
||||||
|
} else { |
||||||
|
return Uri.fromFile(file); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public String getSourceFilePath() { |
||||||
|
return mSourceFilePath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* deal with the system camera's shot. |
||||||
|
*/ |
||||||
|
public boolean onActivityResult(final int requestCode, final int resultCode) { |
||||||
|
if (requestCode != REQ_CODE_CAMERA) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (resultCode != Activity.RESULT_OK) { |
||||||
|
callbackError(); |
||||||
|
return false; |
||||||
|
} |
||||||
|
FutureTask<Boolean> task = BoxingExecutor.getInstance().runWorker(new Callable<Boolean>() { |
||||||
|
@Override |
||||||
|
public Boolean call() throws Exception { |
||||||
|
return rotateImage(resultCode); |
||||||
|
} |
||||||
|
}); |
||||||
|
try { |
||||||
|
if (task != null && task.get()) { |
||||||
|
callbackFinish(); |
||||||
|
} else { |
||||||
|
callbackError(); |
||||||
|
} |
||||||
|
} catch (InterruptedException | ExecutionException ignore) { |
||||||
|
callbackError(); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean rotateSourceFile(File file) throws IOException { |
||||||
|
if (file == null || !file.exists()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
FileOutputStream outputStream = null; |
||||||
|
Bitmap bitmap = null; |
||||||
|
Bitmap outBitmap = null; |
||||||
|
try { |
||||||
|
int degree = BoxingExifHelper.getRotateDegree(file.getAbsolutePath()); |
||||||
|
if (degree == 0) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
int quality = file.length() >= MAX_CAMER_PHOTO_SIZE ? 90 : 100; |
||||||
|
Matrix matrix = new Matrix(); |
||||||
|
matrix.postRotate(degree); |
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options(); |
||||||
|
options.inJustDecodeBounds = false; |
||||||
|
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); |
||||||
|
outBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); |
||||||
|
outputStream = new FileOutputStream(file); |
||||||
|
outBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream); |
||||||
|
outputStream.flush(); |
||||||
|
return true; |
||||||
|
} finally { |
||||||
|
if (outputStream != null) { |
||||||
|
try { |
||||||
|
outputStream.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
BoxingLog.d("IOException when output stream closing!"); |
||||||
|
} |
||||||
|
} |
||||||
|
if (bitmap != null) { |
||||||
|
bitmap.recycle(); |
||||||
|
} |
||||||
|
if (outBitmap != null) { |
||||||
|
outBitmap.recycle(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean rotateImage(int resultCode) throws IOException { |
||||||
|
return resultCode == Activity.RESULT_OK && rotateSourceFile(mOutputFile); |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
mOutputFile = null; |
||||||
|
} |
||||||
|
|
||||||
|
private static class SavedState implements Parcelable { |
||||||
|
private File mOutputFile; |
||||||
|
private String mSourceFilePath; |
||||||
|
|
||||||
|
SavedState() { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
dest.writeSerializable(this.mOutputFile); |
||||||
|
dest.writeString(this.mSourceFilePath); |
||||||
|
} |
||||||
|
|
||||||
|
SavedState(Parcel in) { |
||||||
|
this.mOutputFile = (File) in.readSerializable(); |
||||||
|
this.mSourceFilePath = in.readString(); |
||||||
|
} |
||||||
|
|
||||||
|
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { |
||||||
|
@Override |
||||||
|
public SavedState createFromParcel(Parcel source) { |
||||||
|
return new SavedState(source); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SavedState[] newArray(int size) { |
||||||
|
return new SavedState[size]; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.utils; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
|
||||||
|
import com.bilibili.boxing.model.entity.impl.ImageMedia; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.concurrent.Callable; |
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
import java.util.concurrent.FutureTask; |
||||||
|
|
||||||
|
/** |
||||||
|
* A compress task for {@link ImageMedia} |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class CompressTask { |
||||||
|
public static boolean compress(Context context, final ImageMedia image) { |
||||||
|
return compress(new ImageCompressor(context), image, ImageCompressor.MAX_LIMIT_SIZE_LONG); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param imageCompressor see {@link ImageCompressor}. |
||||||
|
* @param maxSize the proximate max size for compression |
||||||
|
* @return may be a little bigger than expected for performance. |
||||||
|
*/ |
||||||
|
public static boolean compress(final ImageCompressor imageCompressor, final ImageMedia image, final long maxSize) { |
||||||
|
if (imageCompressor == null || image == null || maxSize <= 0) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
FutureTask<Boolean> task = BoxingExecutor.getInstance().runWorker(new Callable<Boolean>() { |
||||||
|
@Override |
||||||
|
public Boolean call() throws Exception { |
||||||
|
final String path = image.getPath(); |
||||||
|
File compressSaveFile = imageCompressor.getCompressOutFile(path); |
||||||
|
File needCompressFile = new File(path); |
||||||
|
if (BoxingFileHelper.isFileValid(compressSaveFile)) { |
||||||
|
image.setCompressPath(compressSaveFile.getAbsolutePath()); |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (!BoxingFileHelper.isFileValid(needCompressFile)) { |
||||||
|
return false; |
||||||
|
} else if (image.getSize() < maxSize) { |
||||||
|
image.setCompressPath(path); |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
try { |
||||||
|
File result = imageCompressor.compress(needCompressFile, maxSize); |
||||||
|
boolean suc = BoxingFileHelper.isFileValid(result); |
||||||
|
image.setCompressPath(suc ? result.getAbsolutePath() : null); |
||||||
|
return suc; |
||||||
|
} catch (IOException | OutOfMemoryError | NullPointerException | IllegalArgumentException e) { |
||||||
|
image.setCompressPath(null); |
||||||
|
BoxingLog.d("image compress fail!"); |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
}); |
||||||
|
try { |
||||||
|
return task != null && task.get(); |
||||||
|
} catch (InterruptedException | ExecutionException ignore) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,351 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2017 Bilibili |
||||||
|
* |
||||||
|
* 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.bilibili.boxing.utils; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.BitmapFactory; |
||||||
|
import android.graphics.Matrix; |
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.OutputStream; |
||||||
|
import java.io.UnsupportedEncodingException; |
||||||
|
import java.security.MessageDigest; |
||||||
|
import java.security.NoSuchAlgorithmException; |
||||||
|
import java.util.Locale; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A compress for image. |
||||||
|
* |
||||||
|
* @author ChenSL |
||||||
|
*/ |
||||||
|
public class ImageCompressor { |
||||||
|
public static final long MAX_LIMIT_SIZE_LONG = 1024 * 1024L; |
||||||
|
|
||||||
|
private static final char HEX_DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; |
||||||
|
private static final int MAX_WIDTH = 3024; |
||||||
|
private static final int MAX_HEIGHT = 4032; |
||||||
|
private static final long MAX_LIMIT_SIZE = 300 * 1024L; |
||||||
|
|
||||||
|
private static final String COMPRESS_FILE_PREFIX = "compress-"; |
||||||
|
|
||||||
|
private File mOutFileFile; |
||||||
|
|
||||||
|
public ImageCompressor(@NonNull File cachedRootDir) { |
||||||
|
if (cachedRootDir != null) { |
||||||
|
mOutFileFile = new File(cachedRootDir.getAbsolutePath() + File.separator + ".compress" + File.separator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public ImageCompressor(@NonNull Context context) { |
||||||
|
if (context != null) { |
||||||
|
String rootDir = BoxingFileHelper.getCacheDir(context); |
||||||
|
if (TextUtils.isEmpty(rootDir)) { |
||||||
|
throw new IllegalStateException("the cache dir is null"); |
||||||
|
} |
||||||
|
mOutFileFile = new File(rootDir + File.separator + ".compress" + File.separator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public File compress(@NonNull File file) throws IOException, NullPointerException, IllegalArgumentException { |
||||||
|
return compress(file, MAX_LIMIT_SIZE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param file file to compress. |
||||||
|
* @param maxsize the proximate max size for compression, not for the image with large ratio. |
||||||
|
* @return may be a little bigger than expected for performance. |
||||||
|
*/ |
||||||
|
public File compress(@NonNull File file, long maxsize) throws IOException, NullPointerException, IllegalArgumentException { |
||||||
|
if (!file.exists()) { |
||||||
|
throw new IllegalArgumentException("file not found : " + file.getAbsolutePath()); |
||||||
|
} |
||||||
|
if (!isLegalFile(file)) { |
||||||
|
throw new IllegalArgumentException("file is not a legal file : " + file.getAbsolutePath()); |
||||||
|
} |
||||||
|
if (mOutFileFile == null) { |
||||||
|
throw new NullPointerException("the external cache dir is null"); |
||||||
|
} |
||||||
|
BitmapFactory.Options checkOptions = new BitmapFactory.Options(); |
||||||
|
checkOptions.inJustDecodeBounds = true; |
||||||
|
String absPath = file.getAbsolutePath(); |
||||||
|
int angle = BoxingExifHelper.getRotateDegree(absPath); |
||||||
|
BitmapFactory.decodeFile(absPath, checkOptions); |
||||||
|
|
||||||
|
if (checkOptions.outWidth <= 0 || checkOptions.outHeight <= 0) { |
||||||
|
throw new IllegalArgumentException("file is not a legal bitmap with 0 with or 0 height : " + file.getAbsolutePath()); |
||||||
|
} |
||||||
|
int width = checkOptions.outWidth; |
||||||
|
int height = checkOptions.outHeight; |
||||||
|
File outFile = createCompressFile(file); |
||||||
|
if (outFile == null) { |
||||||
|
throw new NullPointerException("the compressed file create fail, the compressed path is null."); |
||||||
|
} |
||||||
|
if (!isLargeRatio(width, height)) { |
||||||
|
int[] display = getCompressDisplay(width, height); |
||||||
|
Bitmap bitmap = compressDisplay(absPath, display[0], display[1]); |
||||||
|
Bitmap rotatedBitmap = rotatingImage(angle, bitmap); |
||||||
|
if (bitmap != rotatedBitmap) { |
||||||
|
bitmap.recycle(); |
||||||
|
} |
||||||
|
saveBitmap(rotatedBitmap, outFile); |
||||||
|
rotatedBitmap.recycle(); |
||||||
|
compressQuality(outFile, maxsize, 20); |
||||||
|
} else { |
||||||
|
if (checkOptions.outHeight >= MAX_HEIGHT && checkOptions.outWidth >= MAX_WIDTH) { |
||||||
|
checkOptions.inSampleSize = 2; |
||||||
|
} |
||||||
|
checkOptions.inJustDecodeBounds = false; |
||||||
|
Bitmap originBitmap = BitmapFactory.decodeFile(absPath, checkOptions); |
||||||
|
Bitmap rotatedBitmap = rotatingImage(angle, originBitmap); |
||||||
|
if (originBitmap != rotatedBitmap) { |
||||||
|
originBitmap.recycle(); |
||||||
|
} |
||||||
|
saveBitmap(originBitmap, outFile); |
||||||
|
rotatedBitmap.recycle(); |
||||||
|
compressQuality(outFile, MAX_LIMIT_SIZE_LONG, 50); |
||||||
|
} |
||||||
|
BoxingLog.d("compress suc: " + outFile.getAbsolutePath()); |
||||||
|
return outFile; |
||||||
|
} |
||||||
|
|
||||||
|
private Bitmap rotatingImage(int angle, Bitmap bitmap) { |
||||||
|
if (angle == 0) { |
||||||
|
return bitmap; |
||||||
|
} |
||||||
|
//rotate image
|
||||||
|
Matrix matrix = new Matrix(); |
||||||
|
matrix.postRotate(angle); |
||||||
|
|
||||||
|
//create a new image
|
||||||
|
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); |
||||||
|
} |
||||||
|
|
||||||
|
private void saveBitmap(Bitmap bitmap, File outFile) throws IOException { |
||||||
|
FileOutputStream fos = new FileOutputStream(outFile); |
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); |
||||||
|
try { |
||||||
|
fos.flush(); |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
if (fos != null) { |
||||||
|
fos.close(); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
BoxingLog.d("IOException when saving a bitmap"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param width must > 0 |
||||||
|
* @param height must > 0 |
||||||
|
*/ |
||||||
|
private int[] getCompressDisplay(int width, int height) { |
||||||
|
int thumbWidth = width % 2 == 1 ? width + 1 : width; |
||||||
|
int thumbHeight = height % 2 == 1 ? height + 1 : height; |
||||||
|
int[] results = new int[]{thumbWidth, thumbHeight}; |
||||||
|
|
||||||
|
width = thumbWidth > thumbHeight ? thumbHeight : thumbWidth; |
||||||
|
height = thumbWidth > thumbHeight ? thumbWidth : thumbHeight; |
||||||
|
float scale = (float) width / height; |
||||||
|
if (scale <= 1 && scale >= 0.5625) { |
||||||
|
if (height < 1664) { |
||||||
|
thumbWidth = width; |
||||||
|
thumbHeight = height; |
||||||
|
} else if (height >= 1664 && height < 4990) { |
||||||
|
thumbWidth = width / 2; |
||||||
|
thumbHeight = height / 2; |
||||||
|
} else if (height >= 4990 && height < 10240) { |
||||||
|
thumbWidth = width / 4; |
||||||
|
thumbHeight = height / 4; |
||||||
|
} else { |
||||||
|
int multiple = height / 1280 == 0 ? 1 : height / 1280; |
||||||
|
thumbWidth = width / multiple; |
||||||
|
thumbHeight = height / multiple; |
||||||
|
} |
||||||
|
} else if (scale <= 0.5625 && scale > 0.5) { |
||||||
|
if (height < 1280) { |
||||||
|
thumbWidth = width; |
||||||
|
thumbHeight = height; |
||||||
|
} else { |
||||||
|
int multiple = height / 1280 == 0 ? 1 : height / 1280; |
||||||
|
thumbWidth = width / multiple; |
||||||
|
thumbHeight = height / multiple; |
||||||
|
} |
||||||
|
} else { |
||||||
|
int multiple = (int) Math.ceil(height / (1280.0 / scale)); |
||||||
|
thumbWidth = width / multiple; |
||||||
|
thumbHeight = height / multiple; |
||||||
|
} |
||||||
|
results[0] = thumbWidth; |
||||||
|
results[1] = thumbHeight; |
||||||
|
return results; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param width must > 0 |
||||||
|
* @param height must > 0 |
||||||
|
*/ |
||||||
|
private Bitmap compressDisplay(String imagePath, int width, int height) { |
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options(); |
||||||
|
options.inJustDecodeBounds = true; |
||||||
|
BitmapFactory.decodeFile(imagePath, options); |
||||||
|
|
||||||
|
int outH = options.outHeight; |
||||||
|
int outW = options.outWidth; |
||||||
|
int inSampleSize = 1; |
||||||
|
|
||||||
|
if (outH > height || outW > width) { |
||||||
|
int halfH = outH / 2; |
||||||
|
int halfW = outW / 2; |
||||||
|
while ((halfH / inSampleSize) > height && (halfW / inSampleSize) > width) { |
||||||
|
inSampleSize *= 2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
options.inSampleSize = inSampleSize; |
||||||
|
|
||||||
|
options.inJustDecodeBounds = false; |
||||||
|
|
||||||
|
int heightRatio = (int) Math.ceil(options.outHeight / (float) height); |
||||||
|
int widthRatio = (int) Math.ceil(options.outWidth / (float) width); |
||||||
|
|
||||||
|
if (heightRatio > 1 || widthRatio > 1) { |
||||||
|
if (heightRatio > widthRatio) { |
||||||
|
options.inSampleSize = heightRatio; |
||||||
|
} else { |
||||||
|
options.inSampleSize = widthRatio; |
||||||
|
} |
||||||
|
} |
||||||
|
options.inJustDecodeBounds = false; |
||||||
|
|
||||||
|
return BitmapFactory.decodeFile(imagePath, options); |
||||||
|
} |
||||||
|
|
||||||
|
private void compressQuality(File outFile, long maxSize, int maxQuality) throws IOException { |
||||||
|
long length = outFile.length(); |
||||||
|
int quality = 90; |
||||||
|
if (length > maxSize) { |
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
||||||
|
BoxingLog.d("source file size : " + outFile.length() + ",path : " + outFile); |
||||||
|
while (true) { |
||||||
|
compressPhotoByQuality(outFile, bos, quality); |
||||||
|
long size = bos.size(); |
||||||
|
BoxingLog.d("compressed file size : " + size); |
||||||
|
if (quality <= maxQuality) { |
||||||
|
break; |
||||||
|
} |
||||||
|
if (size < maxSize) { |
||||||
|
break; |
||||||
|
} else { |
||||||
|
quality -= 10; |
||||||
|
bos.reset(); |
||||||
|
} |
||||||
|
} |
||||||
|
OutputStream fos = new FileOutputStream(outFile); |
||||||
|
bos.writeTo(fos); |
||||||
|
bos.flush(); |
||||||
|
fos.close(); |
||||||
|
bos.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void compressPhotoByQuality(File file, final OutputStream os, final int quality) throws IOException, OutOfMemoryError { |
||||||
|
BoxingLog.d("start compress quality... "); |
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options(); |
||||||
|
options.inPreferredConfig = Bitmap.Config.RGB_565; |
||||||
|
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); |
||||||
|
if (bitmap != null) { |
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os); |
||||||
|
bitmap.recycle(); |
||||||
|
} else { |
||||||
|
throw new NullPointerException("bitmap is null when compress by quality"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private File createCompressFile(File file) throws IOException { |
||||||
|
File outFile = getCompressOutFile(file); |
||||||
|
if (!mOutFileFile.exists()) { |
||||||
|
mOutFileFile.mkdirs(); |
||||||
|
} |
||||||
|
BoxingLog.d("compress out file : " + outFile); |
||||||
|
outFile.createNewFile(); |
||||||
|
return outFile; |
||||||
|
} |
||||||
|
|
||||||
|
public @Nullable File getCompressOutFile(File file) { |
||||||
|
String path = getCompressOutFilePath(file); |
||||||
|
return TextUtils.isEmpty(path) ? null: new File(path); |
||||||
|
} |
||||||
|
|
||||||
|
public @Nullable File getCompressOutFile(String filePth) { |
||||||
|
String path = getCompressOutFilePath(filePth); |
||||||
|
return TextUtils.isEmpty(path) ? null: new File(path); |
||||||
|
} |
||||||
|
|
||||||
|
public @Nullable String getCompressOutFilePath(File file) { |
||||||
|
return getCompressOutFilePath(file.getAbsolutePath()); |
||||||
|
} |
||||||
|
|
||||||
|
public @Nullable String getCompressOutFilePath(String filePath) { |
||||||
|
try { |
||||||
|
return mOutFileFile + File.separator + COMPRESS_FILE_PREFIX + signMD5(filePath.getBytes("UTF-8")) + ".jpg"; |
||||||
|
} catch (UnsupportedEncodingException e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public String signMD5(byte[] source) { |
||||||
|
try { |
||||||
|
MessageDigest digest = MessageDigest.getInstance("MD5"); |
||||||
|
return signDigest(source, digest); |
||||||
|
} catch (NoSuchAlgorithmException e) { |
||||||
|
BoxingLog.d("have no md5"); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private String signDigest(byte[] source, MessageDigest digest) { |
||||||
|
digest.update(source); |
||||||
|
byte[] data = digest.digest(); |
||||||
|
int j = data.length; |
||||||
|
char str[] = new char[j * 2]; |
||||||
|
int k = 0; |
||||||
|
for (byte byte0 : data) { |
||||||
|
str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf]; |
||||||
|
str[k++] = HEX_DIGITS[byte0 & 0xf]; |
||||||
|
} |
||||||
|
return new String(str).toLowerCase(Locale.getDefault()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isLargeRatio(int width, int height) { |
||||||
|
return width / height >= 3 || height / width >= 3; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isLegalFile(File file) { |
||||||
|
return file != null && file.exists() && file.isFile() && file.length() > 0; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<resources> |
||||||
|
|
||||||
|
</resources> |
@ -0,0 +1,24 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ Copyright (C) 2017 Bilibili |
||||||
|
~ |
||||||
|
~ 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. |
||||||
|
~ |
||||||
|
--> |
||||||
|
|
||||||
|
<paths> |
||||||
|
<cache-path name="internal" path="boxing" /> |
||||||
|
|
||||||
|
<external-path name="external" path="DCIM/bili/boxing" /> |
||||||
|
|
||||||
|
</paths> |
@ -1,28 +1,30 @@ |
|||||||
<manifest package="com.ztiany.mediaselector" |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
xmlns:android="http://schemas.android.com/apk/res/android"> |
package="com.ztiany.mediaselector"> |
||||||
|
|
||||||
<application> |
<application> |
||||||
|
|
||||||
<activity |
<activity |
||||||
android:name="com.yalantis.ucrop.UCropActivity" |
android:name="com.yalantis.ucrop.UCropActivity" |
||||||
android:screenOrientation="portrait" |
android:screenOrientation="portrait" |
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> |
android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> |
||||||
|
|
||||||
<activity |
<activity |
||||||
android:name=".LimitedBoxingActivity" |
android:name="com.bilibili.boxing_impl.ui.BoxingActivity" |
||||||
android:launchMode="singleTop" |
android:launchMode="singleTop" |
||||||
android:screenOrientation="portrait" |
android:screenOrientation="portrait" |
||||||
android:theme="@style/Boxing.AppTheme.NoActionBar"> |
android:theme="@style/Boxing.AppTheme.NoActionBar" /> |
||||||
</activity> |
|
||||||
|
|
||||||
<activity |
<activity |
||||||
android:name=".LimitedBoxingViewActivity" |
android:name="com.bilibili.boxing_impl.ui.BoxingViewActivity" |
||||||
android:launchMode="singleTop" |
android:launchMode="singleTop" |
||||||
android:screenOrientation="portrait" |
android:screenOrientation="portrait" |
||||||
android:theme="@style/Boxing.AppTheme.NoActionBar"> |
android:theme="@style/Boxing.AppTheme.NoActionBar" /> |
||||||
</activity> |
|
||||||
|
|
||||||
|
<activity |
||||||
|
android:name="com.bilibili.boxing_impl.ui.BoxingBottomSheetActivity" |
||||||
|
android:screenOrientation="portrait" |
||||||
|
android:theme="@style/Boxing.AppTheme.NoActionBar" /> |
||||||
|
|
||||||
</application> |
</application> |
||||||
|
|
||||||
</manifest> |
</manifest> |