@ -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> |