Compare commits
5
Commits
8839320484
...
dev_lvmeng
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bc6d2f2c5 | ||
|
|
3dea103e27 | ||
|
|
5a4f136dba | ||
|
|
4973236469 | ||
|
|
c02a2ff08c |
@@ -78,7 +78,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
|
||||
api rootProject.ext.androidxLibs
|
||||
// ViewModel and LiveData
|
||||
api rootProject.ext.jetpackLibs
|
||||
@@ -112,4 +112,7 @@ dependencies {
|
||||
|
||||
implementation 'androidx.fragment:fragment-ktx:1.6.0'
|
||||
|
||||
// implementation 'com.zhihu.android:matisse:0.6.0'
|
||||
implementation project(':matisse')
|
||||
implementation 'com.github.hackware1993:MagicIndicator:1.7.0'
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
package="com.zmkg.coaloperation">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
@@ -17,6 +18,7 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:replace="android:allowBackup">
|
||||
|
||||
@@ -69,6 +71,7 @@
|
||||
<activity android:name=".ui.wateryield.WaterYieldObserveRecordActivity" />
|
||||
<activity android:name=".ui.geologging.GeologicalPointActivity" />
|
||||
<activity android:name=".ui.geologging.AddGeologicalPointActivity" />
|
||||
<activity android:name=".ui.geologging.AddGeoLoggingFaceActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.zmkg.coaloperation
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
@@ -10,7 +11,7 @@ import com.zmkg.coaloperation.bottomtab.HomeBottomTabLayout
|
||||
import com.zmkg.coaloperation.databinding.ActivityMainBinding
|
||||
import com.zmkg.coaloperation.ui.home.fragment.HomeFragment
|
||||
import com.zmkg.coaloperation.ui.record.fragment.RecordFragment
|
||||
import com.zmkg.coaloperation.ui.report.fragment.ReportFragment
|
||||
import com.zmkg.coaloperation.ui.report.fragment.Report2Fragment
|
||||
import com.zmkg.coaloperation.ui.user.fragment.UserFragment
|
||||
|
||||
class MainActivity : BaseVMBActivity<TestViewModel, ActivityMainBinding>(R.layout.activity_main),
|
||||
@@ -56,7 +57,7 @@ class MainActivity : BaseVMBActivity<TestViewModel, ActivityMainBinding>(R.layou
|
||||
}
|
||||
|
||||
TAG_REPORT -> {
|
||||
return ReportFragment()
|
||||
return Report2Fragment()
|
||||
}
|
||||
|
||||
// TAG_MESSAGE -> {
|
||||
@@ -77,4 +78,5 @@ class MainActivity : BaseVMBActivity<TestViewModel, ActivityMainBinding>(R.layou
|
||||
override fun transparentStatusBar(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,6 +73,8 @@ class MyApplication : Application(), ViewModelStoreOwner {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun initLoginStatusListener() {
|
||||
// TUILogin.addLoginListener(loginStatusListener)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.zmkg.coaloperation.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.bean.CommonImageBean
|
||||
import com.zmkg.coaloperation.databinding.ListItemCommonImageBinding
|
||||
import com.zmkg.coaloperation.superfuntion.loadRoundedImage
|
||||
import com.zmkg.coaloperation.utils.dp
|
||||
import com.zmkg.coaloperation.utils.load
|
||||
|
||||
class CommonImageAdapter(var list: MutableList<CommonImageBean>, var imageSize: Int = 70.dp):
|
||||
BaseQuickAdapter<CommonImageBean, CommonImageAdapter.VH>(
|
||||
R.layout.list_item_common_image, list
|
||||
){
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val binding = ListItemCommonImageBinding.inflate(
|
||||
LayoutInflater.from(context), parent, false
|
||||
)
|
||||
return VH(binding).also {
|
||||
bindViewClickListener(it, viewType)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
holder: VH,
|
||||
item: CommonImageBean
|
||||
) {
|
||||
holder.binding.ivImage.let {
|
||||
it.updateLayoutParams {
|
||||
width = imageSize
|
||||
height = imageSize
|
||||
}
|
||||
if (item.isAddFlag) {
|
||||
it.load(R.drawable.ic_add_image)
|
||||
} else {
|
||||
it.loadRoundedImage(item.imagePath, 3F)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class VH(var binding: ListItemCommonImageBinding): BaseViewHolder(binding.root)
|
||||
|
||||
}
|
||||
+9
-11
@@ -5,30 +5,28 @@ import android.view.ViewGroup
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.bean.GeoPointImage
|
||||
import com.zmkg.coaloperation.databinding.ListItemPointImageBinding
|
||||
import com.zmkg.coaloperation.databinding.ItemGeologicalListBinding
|
||||
import com.zmkg.coaloperation.db.entity.geologging.GeoLoggingFace
|
||||
|
||||
class GeoPointImageAdapter(list: MutableList<GeoPointImage>):
|
||||
BaseQuickAdapter<GeoPointImage, GeoPointImageAdapter.VH>(
|
||||
R.layout.list_item_point_image, list
|
||||
class GeoLoggingFaceAdapter(list: MutableList<GeoLoggingFace>):
|
||||
BaseQuickAdapter<GeoLoggingFace, GeoLoggingFaceAdapter.VH>(
|
||||
R.layout.item_geological_list, list
|
||||
){
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val binding = ListItemPointImageBinding.inflate(
|
||||
val binding = ItemGeologicalListBinding.inflate(
|
||||
LayoutInflater.from(context), parent, false
|
||||
)
|
||||
return VH(binding).also {
|
||||
bindViewClickListener(it, viewType)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
holder: VH,
|
||||
item: GeoPointImage
|
||||
item: GeoLoggingFace
|
||||
) {
|
||||
holder.binding.ivPointImage.setImageResource(R.drawable.ic_add_image)
|
||||
holder.binding.tvFaceName.text = item.surfaceName
|
||||
}
|
||||
|
||||
inner class VH(var binding: ListItemPointImageBinding): BaseViewHolder(binding.root)
|
||||
inner class VH(var binding: ItemGeologicalListBinding): BaseViewHolder(binding.root)
|
||||
|
||||
}
|
||||
+61
-41
@@ -1,24 +1,27 @@
|
||||
package com.zmkg.coaloperation.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.chad.library.adapter.base.BaseMultiItemQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
import com.zhihu.matisse.Matisse
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.bean.BaseItem
|
||||
import com.zmkg.coaloperation.bean.GeoPointImage
|
||||
import com.zmkg.coaloperation.base.BaseActivity
|
||||
import com.zmkg.coaloperation.bean.CommonImageBean
|
||||
import com.zmkg.coaloperation.bean.GeoPointMultiItem
|
||||
import com.zmkg.coaloperation.databinding.ListItemPointBaseHorizontalBinding
|
||||
import com.zmkg.coaloperation.databinding.ListItemPointBaseVerticalBinding
|
||||
import com.zmkg.coaloperation.utils.GalleryUtils
|
||||
import com.zmkg.coaloperation.utils.gone
|
||||
import com.zmkg.coaloperation.utils.permission.StorageUtils
|
||||
import com.zmkg.coaloperation.utils.visible
|
||||
|
||||
class GeoPointMultiAdapter(list: MutableList<GeoPointMultiItem>):
|
||||
BaseMultiItemQuickAdapter<GeoPointMultiItem, BaseViewHolder>(list){
|
||||
class GeoPointMultiAdapter(list: MutableList<GeoPointMultiItem>) :
|
||||
BaseMultiItemQuickAdapter<GeoPointMultiItem, BaseViewHolder>(list) {
|
||||
|
||||
init {
|
||||
addItemType(GeoPointMultiItem.TYPE_BASE_INFO, R.layout.list_item_point_base_info)
|
||||
@@ -32,83 +35,100 @@ class GeoPointMultiAdapter(list: MutableList<GeoPointMultiItem>):
|
||||
item: GeoPointMultiItem
|
||||
) {
|
||||
when (holder.itemViewType) {
|
||||
GeoPointMultiItem.TYPE_BASE_INFO->{
|
||||
GeoPointMultiItem.TYPE_BASE_INFO -> {
|
||||
loadBaseInfo(holder, item)
|
||||
}
|
||||
GeoPointMultiItem.TYPE_LOGGING_INFO->{
|
||||
|
||||
GeoPointMultiItem.TYPE_LOGGING_INFO -> {
|
||||
loadLoggingInfo(holder, item)
|
||||
}
|
||||
GeoPointMultiItem.TYPE_CALCULATE_INFO->{
|
||||
|
||||
GeoPointMultiItem.TYPE_CALCULATE_INFO -> {
|
||||
loadCalculateInfo(holder, item)
|
||||
}
|
||||
GeoPointMultiItem.TYPE_UPLOAD_IMAGE->{
|
||||
|
||||
GeoPointMultiItem.TYPE_UPLOAD_IMAGE -> {
|
||||
loadUploadImage(holder, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadUploadImage(holder: BaseViewHolder,item: GeoPointMultiItem) {
|
||||
private fun loadUploadImage(holder: BaseViewHolder, item: GeoPointMultiItem) {
|
||||
val rvImageList = holder.itemView.findViewById<RecyclerView>(R.id.rvImageList)
|
||||
val images = mutableListOf(
|
||||
GeoPointImage(),
|
||||
GeoPointImage(),
|
||||
GeoPointImage(),
|
||||
GeoPointImage()
|
||||
)
|
||||
rvImageList.let {rv->
|
||||
if (item.images == null) {
|
||||
item.images = mutableListOf()
|
||||
}
|
||||
val images = item.images!!
|
||||
rvImageList.let { rv ->
|
||||
rv.layoutManager = GridLayoutManager(rv.context, 4, GridLayoutManager.VERTICAL, false)
|
||||
rv.adapter = GeoPointImageAdapter(images).also {
|
||||
rv.adapter = CommonImageAdapter(images).also {
|
||||
it.setOnItemClickListener { adapter, view, position ->
|
||||
|
||||
if (it.data[position].isAddFlag.not()) {
|
||||
return@setOnItemClickListener
|
||||
}
|
||||
openGallery(context as BaseActivity, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCalculateInfo(holder: BaseViewHolder,item: GeoPointMultiItem) {}
|
||||
private fun loadCalculateInfo(holder: BaseViewHolder, item: GeoPointMultiItem) {}
|
||||
|
||||
private fun loadLoggingInfo(holder: BaseViewHolder,item: GeoPointMultiItem) {
|
||||
private fun loadLoggingInfo(holder: BaseViewHolder, item: GeoPointMultiItem) {
|
||||
|
||||
}
|
||||
|
||||
private fun loadBaseInfo(holder: BaseViewHolder,item: GeoPointMultiItem) {
|
||||
private fun loadBaseInfo(holder: BaseViewHolder, item: GeoPointMultiItem) {
|
||||
val llBaseTop = holder.itemView.findViewById<LinearLayout>(R.id.llBaseTop)
|
||||
val topList = mutableListOf(
|
||||
BaseItem(name = "点号", value = "1#"),
|
||||
BaseItem(name = "点距", value = "1#"),
|
||||
BaseItem(name = "累距", value = "78.874"),
|
||||
llBaseTop.removeAllViews()
|
||||
item.baseTopList?.forEach {
|
||||
val itemBinding = ListItemPointBaseHorizontalBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
llBaseTop,
|
||||
false
|
||||
)
|
||||
val bottomList = mutableListOf(
|
||||
BaseItem(name = "巷高", value = "2.855"),
|
||||
BaseItem(name = "顶板", value = "1024.491"),
|
||||
BaseItem(name = "底板", value = "1018.491"),
|
||||
BaseItem(name = "巷顶煤厚", value = "0.15"),
|
||||
)
|
||||
topList.forEach {
|
||||
val itemBinding = ListItemPointBaseHorizontalBinding.inflate(LayoutInflater.from(context), llBaseTop, false)
|
||||
itemBinding.run {
|
||||
tvItemName.text = it.name
|
||||
tvItemValue.text = it.value
|
||||
etInputValue.setText(it.value)
|
||||
}
|
||||
llBaseTop.addView(itemBinding.root)
|
||||
}
|
||||
val llBaseBottom = holder.itemView.findViewById<LinearLayout>(R.id.llBaseBottom)
|
||||
|
||||
bottomList.forEachIndexed {index, it ->
|
||||
val itemBinding = ListItemPointBaseVerticalBinding.inflate(LayoutInflater.from(context),llBaseBottom, false)
|
||||
val llBaseBottom = holder.itemView.findViewById<LinearLayout>(R.id.llBaseBottom)
|
||||
llBaseBottom.removeAllViews()
|
||||
item.baseBottomList?.forEachIndexed { index, it ->
|
||||
val itemBinding = ListItemPointBaseVerticalBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
llBaseBottom,
|
||||
false
|
||||
)
|
||||
itemBinding.run {
|
||||
tvItemName.text = it.name
|
||||
tvItemValue.text = it.value
|
||||
etInputValue.setText(it.value)
|
||||
dividerVertical.run {
|
||||
if (index == bottomList.size - 1) gone() else visible()
|
||||
if (index == item.baseBottomList!!.size - 1) gone() else visible()
|
||||
}
|
||||
}
|
||||
itemBinding.root.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
width = 0
|
||||
height = LinearLayout.LayoutParams.MATCH_PARENT
|
||||
weight = if (index == bottomList.size - 1) 1.24F else 1.0F
|
||||
weight = if (index == item.baseBottomList!!.size - 1) 1.24F else 1.0F
|
||||
}
|
||||
llBaseBottom.addView(itemBinding.root)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun openGallery(activity: BaseActivity, adapter: CommonImageAdapter) {
|
||||
StorageUtils.requestPermission(activity) {
|
||||
GalleryUtils.openGallery(activity) { data ->
|
||||
val paths = Matisse.obtainPathResult(data) // 获取文件路径列表
|
||||
paths?.forEach { path ->
|
||||
val size = adapter.list.size
|
||||
adapter.list.add(size - 1, CommonImageBean(imagePath = path))
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.zmkg.coaloperation.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
import com.zhihu.matisse.Matisse
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.base.BaseActivity
|
||||
import com.zmkg.coaloperation.bean.CommonImageBean
|
||||
import com.zmkg.coaloperation.bean.ReportItem
|
||||
import com.zmkg.coaloperation.databinding.ListItemReportBinding
|
||||
import com.zmkg.coaloperation.utils.GalleryUtils
|
||||
import com.zmkg.coaloperation.utils.dp
|
||||
import com.zmkg.coaloperation.utils.gone
|
||||
import com.zmkg.coaloperation.utils.permission.StorageUtils
|
||||
import com.zmkg.coaloperation.utils.visible
|
||||
|
||||
class ReportAdapter(list: MutableList<ReportItem>) :
|
||||
BaseQuickAdapter<ReportItem, ReportAdapter.VH>(R.layout.list_item_report, list) {
|
||||
|
||||
inner class VH(var binding: ListItemReportBinding) : BaseViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val binding = ListItemReportBinding.inflate(
|
||||
LayoutInflater.from(context), parent, false
|
||||
)
|
||||
return VH(binding).also {
|
||||
bindViewClickListener(it, viewType)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convert(holder: VH, item: ReportItem) {
|
||||
val binding = holder.binding
|
||||
val position = holder.layoutPosition
|
||||
binding.tvItemName.run {
|
||||
text = item.name
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
gravity = if (item.viewType == 4) Gravity.CENTER_VERTICAL else Gravity.TOP
|
||||
}
|
||||
}
|
||||
binding.divider.run {
|
||||
if (position == data.size - 1) gone() else visible()
|
||||
}
|
||||
when (item.viewType) {
|
||||
1 -> {
|
||||
binding.tvItemValue.gone()
|
||||
binding.rvReportImage.gone()
|
||||
binding.etInputValue.let { et ->
|
||||
et.visible()
|
||||
et.setText(item.value)
|
||||
et.hint = item.inputHint
|
||||
et.isSingleLine = item.isSingleLine
|
||||
if (item.isSingleLine) {
|
||||
et.gravity = Gravity.CENTER_VERTICAL
|
||||
et.setLines(1)
|
||||
et.updateLayoutParams<LinearLayout.LayoutParams> { height = 50.dp }
|
||||
} else {
|
||||
et.gravity = Gravity.TOP
|
||||
et.setLines(item.inputShowLines)
|
||||
et.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
height = LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
topMargin = 15.dp
|
||||
bottomMargin = 15.dp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2 -> {
|
||||
binding.rvReportImage.gone()
|
||||
binding.etInputValue.gone()
|
||||
binding.tvItemValue.let { tv ->
|
||||
tv.visible()
|
||||
tv.text = item.value
|
||||
tv.setCompoundDrawablesWithIntrinsicBounds(
|
||||
0,
|
||||
0,
|
||||
R.drawable.ic_arrow_down_gray,
|
||||
0
|
||||
)
|
||||
tv.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3 -> {
|
||||
binding.rvReportImage.gone()
|
||||
binding.etInputValue.gone()
|
||||
binding.tvItemValue.let { tv ->
|
||||
tv.visible()
|
||||
tv.text = item.value
|
||||
tv.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_select_time, 0)
|
||||
tv.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
width = LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4 -> {
|
||||
binding.tvItemValue.gone()
|
||||
binding.etInputValue.gone()
|
||||
if (item.images == null) {
|
||||
item.images = mutableListOf()
|
||||
}
|
||||
binding.rvReportImage.let { rv ->
|
||||
rv.visible()
|
||||
rv.layoutManager = GridLayoutManager(rv.context, 2)
|
||||
rv.adapter = CommonImageAdapter(item.images!!, 80.dp).also {
|
||||
it.setOnItemClickListener { adapter, view, position ->
|
||||
if (it.data[position].isAddFlag.not()) {
|
||||
return@setOnItemClickListener
|
||||
}
|
||||
openGallery(context as BaseActivity, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun openGallery(activity: BaseActivity, adapter: CommonImageAdapter) {
|
||||
StorageUtils.requestPermission(activity) {
|
||||
GalleryUtils.openGallery(activity) { data ->
|
||||
val paths = Matisse.obtainPathResult(data) // 获取文件路径列表
|
||||
paths?.forEach { path ->
|
||||
val size = adapter.list.size
|
||||
adapter.list.add(size - 1, CommonImageBean(imagePath = path))
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.zmkg.coaloperation.base;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.zmkg.coaloperation.bean.IActivityResult;
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
|
||||
// public static final String COLOR_WHITE = "#FFFFFF";
|
||||
//
|
||||
// public void whiteStatusBar(boolean isFits) {
|
||||
// whiteStatusBar(isFits, false);
|
||||
// }
|
||||
//
|
||||
// public void whiteStatusBar(boolean isFits, boolean keyboardEnable) {
|
||||
// hasColorStatusBar(COLOR_WHITE, isFits, true, keyboardEnable);
|
||||
// }
|
||||
//
|
||||
// public void transparentStatusBar(boolean isFits, boolean isDarkFont) {
|
||||
// ImmersionBar.with(this)
|
||||
// .fitsSystemWindows(isFits)
|
||||
// .statusBarDarkFont(isDarkFont)
|
||||
// .transparentStatusBar()
|
||||
// .navigationBarColor(R.color.white)
|
||||
// .navigationBarDarkIcon(true)
|
||||
// .init();
|
||||
// }
|
||||
//
|
||||
// public void hasColorStatusBar(String statusBarColor, boolean isFits, boolean isDarkFont) {
|
||||
// hasColorStatusBar(statusBarColor, isFits, isDarkFont, false);
|
||||
// }
|
||||
//
|
||||
// public void hasColorStatusBar(String statusBarColor, boolean isFits, boolean isDarkFont, boolean keyboardEnable) {
|
||||
// ImmersionBar.with(this)
|
||||
// .fitsSystemWindows(isFits)
|
||||
// .statusBarColor(statusBarColor)
|
||||
// .statusBarDarkFont(isDarkFont)
|
||||
// .keyboardEnable(keyboardEnable)
|
||||
// .navigationBarColor(R.color.white)
|
||||
// .navigationBarDarkIcon(true)
|
||||
// .init();
|
||||
// }
|
||||
|
||||
private final ActivityResultLauncher<Intent> activityLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> this.launchActivityCallback.callback(result)
|
||||
);
|
||||
private IActivityResult.LaunchActivityCallback launchActivityCallback;
|
||||
|
||||
public void launchActivityResult(Intent intent, IActivityResult.LaunchActivityCallback activityCallback) {
|
||||
this.launchActivityCallback = activityCallback;
|
||||
activityLauncher.launch(intent);
|
||||
}
|
||||
|
||||
private IActivityResult.LaunchTackPictureCallback launchTackPictureCallback;
|
||||
|
||||
private final ActivityResultLauncher<Uri> tackPictureLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.TakePicture(),
|
||||
result -> this.launchTackPictureCallback.callback(result)
|
||||
);
|
||||
|
||||
public void launchTackPicture(Uri uri, IActivityResult.LaunchTackPictureCallback callback) {
|
||||
this.launchTackPictureCallback = callback;
|
||||
tackPictureLauncher.launch(uri);
|
||||
}
|
||||
|
||||
private IActivityResult.RequestPermissionCallback launchPermissionCallback;
|
||||
|
||||
public void requestSinglePermissionResult(String permission, IActivityResult.RequestPermissionCallback permissionCallback) {
|
||||
this.launchPermissionCallback = permissionCallback;
|
||||
permissionLauncher.launch(permission);
|
||||
}
|
||||
|
||||
private final ActivityResultLauncher<String> permissionLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> this.launchPermissionCallback.callback(result)
|
||||
);
|
||||
|
||||
private IActivityResult.RequestMultiplePermissionsCallback launchMultiplePermissionsCallback;
|
||||
|
||||
public void requestMultiplePermissionsResult(String[] permissions, IActivityResult.RequestMultiplePermissionsCallback permissionCallback) {
|
||||
this.launchMultiplePermissionsCallback = permissionCallback;
|
||||
multiplePermissionLauncher.launch(permissions);
|
||||
}
|
||||
|
||||
private final ActivityResultLauncher<String[]> multiplePermissionLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
result -> this.launchMultiplePermissionsCallback.callback(result)
|
||||
);
|
||||
|
||||
}
|
||||
@@ -55,7 +55,7 @@ import java.sql.SQLException
|
||||
* 封装了ViewModel和DataBinding的Activity基类
|
||||
*/
|
||||
abstract class BaseVMBActivity<VM : BaseViewModel, B : ViewDataBinding>(private val contentViewResId: Int) :
|
||||
AppCompatActivity(), View.OnClickListener {
|
||||
BaseActivity(), View.OnClickListener {
|
||||
var mContext: Context? = null
|
||||
var mActivity: Activity? = null
|
||||
lateinit var mViewModel: VM
|
||||
@@ -447,4 +447,5 @@ abstract class BaseVMBActivity<VM : BaseViewModel, B : ViewDataBinding>(private
|
||||
return "0"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.zmkg.coaloperation.bean;
|
||||
|
||||
import androidx.activity.result.ActivityResult;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface IActivityResult {
|
||||
|
||||
interface LaunchActivityCallback {
|
||||
void callback(ActivityResult result);
|
||||
}
|
||||
|
||||
interface LaunchTackPictureCallback {
|
||||
void callback(boolean result);
|
||||
}
|
||||
|
||||
interface RequestPermissionCallback {
|
||||
void callback(boolean result);
|
||||
}
|
||||
|
||||
interface RequestMultiplePermissionsCallback {
|
||||
void callback(Map<String,Boolean> result);
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,10 @@ data class GeoPointMultiItem(
|
||||
var pointName: String = "",
|
||||
var pointDistance: String = "",
|
||||
var totalDistance: String = "",
|
||||
): MultiItemEntity {
|
||||
var baseTopList: MutableList<BaseItem>? = null,
|
||||
var baseBottomList: MutableList<BaseItem>? = null,
|
||||
var images: MutableList<CommonImageBean>? = null
|
||||
) : MultiItemEntity {
|
||||
companion object {
|
||||
const val TYPE_BASE_INFO = 1
|
||||
const val TYPE_LOGGING_INFO = 2
|
||||
@@ -72,6 +75,19 @@ data class GeoPointMultiItem(
|
||||
}
|
||||
}
|
||||
|
||||
data class GeoPointImage(
|
||||
var imagePath: String = ""
|
||||
data class CommonImageBean(
|
||||
var imagePath: String = "",
|
||||
var isAddFlag: Boolean = false
|
||||
)
|
||||
|
||||
data class ReportItem(
|
||||
var name: String = "",
|
||||
var value: String = "",
|
||||
var id: String = "",
|
||||
//1-输入框,2-文本选择弹窗,3-时间选择弹窗,4-上传图片
|
||||
var viewType: Int = 0,
|
||||
var inputHint: String = "",
|
||||
var inputShowLines: Int = 0,
|
||||
var isSingleLine: Boolean = true,
|
||||
var images: MutableList<CommonImageBean>? = null
|
||||
)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.zmkg.coaloperation.db.entity.geologging
|
||||
|
||||
data class GeoLoggingFace(
|
||||
var surfaceId: Long = 0,
|
||||
|
||||
var userId: String = "",
|
||||
|
||||
/**
|
||||
* 工作面名称
|
||||
*/
|
||||
var surfaceName: String? = "",
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.zmkg.coaloperation.dialog
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import androidx.annotation.StyleRes
|
||||
import com.zmkg.coaloperation.R
|
||||
|
||||
open class BaseDialog(context: Context, @StyleRes dialogTheme: Int) :
|
||||
Dialog(context, dialogTheme) {
|
||||
constructor(context: Context) : this(context, R.style.DialogTheme)
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.zmkg.coaloperation.dialog
|
||||
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.allen.library.shape.ShapeButton
|
||||
import com.zmkg.coaloperation.databinding.DialogCommonBinding
|
||||
import com.zmkg.coaloperation.utils.dp
|
||||
|
||||
class CustomCenterDialog(
|
||||
var activity: FragmentActivity,
|
||||
var defaultWidth: Int = 300.dp
|
||||
) : BaseDialog(activity) {
|
||||
|
||||
private lateinit var binding: DialogCommonBinding
|
||||
|
||||
var dialogCancelable = false
|
||||
var dialogCanceledOnTouchOutside = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DialogCommonBinding.inflate(LayoutInflater.from(activity))
|
||||
setContentView(binding.root)
|
||||
|
||||
setCancelable(dialogCancelable)
|
||||
setCanceledOnTouchOutside(dialogCanceledOnTouchOutside)
|
||||
window?.apply {
|
||||
setLayout(defaultWidth, WindowManager.LayoutParams.WRAP_CONTENT)
|
||||
setBackgroundDrawable(ColorDrawable())
|
||||
setGravity(Gravity.CENTER)
|
||||
}
|
||||
binding.btnCancel.setOnClickListener { onLeftClick?.invoke(this) }
|
||||
binding.btnConfirm.setOnClickListener { onRightClick?.invoke(this) }
|
||||
}
|
||||
|
||||
private var dialogTitle: String = ""
|
||||
fun setDialogTitle(title: String) {
|
||||
this.dialogTitle = title
|
||||
}
|
||||
|
||||
private var dialogContent: String = ""
|
||||
fun setDialogContent(content: String) {
|
||||
this.dialogContent = content
|
||||
}
|
||||
|
||||
private var spannableBuilder: SpannableStringBuilder? = null
|
||||
fun setDialogContent(spannableBuilder: SpannableStringBuilder) {
|
||||
this.spannableBuilder = spannableBuilder
|
||||
}
|
||||
|
||||
private var dialogLeftBtnText = ""
|
||||
private var onLeftClick: ((CustomCenterDialog) -> Unit)? = null
|
||||
fun setCancelButton(
|
||||
text: String,
|
||||
onClick: (CustomCenterDialog) -> Unit
|
||||
) {
|
||||
this.dialogLeftBtnText = text
|
||||
this.onLeftClick = onClick
|
||||
}
|
||||
|
||||
private var dialogRightBtnText = ""
|
||||
private var onRightClick: ((CustomCenterDialog) -> Unit)? = null
|
||||
fun setConfirmButton(
|
||||
text: String,
|
||||
onClick: (CustomCenterDialog) -> Unit
|
||||
) {
|
||||
this.dialogRightBtnText = text
|
||||
this.onRightClick = onClick
|
||||
}
|
||||
|
||||
private var showSingleButton = false
|
||||
fun setShowSingleButton(show: Boolean) {
|
||||
this.showSingleButton = show
|
||||
}
|
||||
|
||||
|
||||
var bgLayoutSetting: ((View) -> Unit)? = null
|
||||
var titleSetting: ((TextView) -> Unit)? = null
|
||||
var contentSetting: ((TextView) -> Unit)? = null
|
||||
var leftBtnSetting: ((ShapeButton) -> Unit)? = null
|
||||
var rightBtnSetting: ((ShapeButton) -> Unit)? = null
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
|
||||
binding.tvDialogTitle.apply {
|
||||
text = dialogTitle
|
||||
}
|
||||
binding.tvDialogContent.apply {
|
||||
text = dialogContent
|
||||
}
|
||||
|
||||
bgLayoutSetting?.invoke(binding.root)
|
||||
titleSetting?.invoke(binding.tvDialogTitle)
|
||||
contentSetting?.invoke(binding.tvDialogContent)
|
||||
leftBtnSetting?.invoke(binding.btnCancel)
|
||||
rightBtnSetting?.invoke(binding.btnConfirm)
|
||||
}
|
||||
}
|
||||
@@ -207,7 +207,7 @@ fun ImageView.loadCircle(@DrawableRes resourceId: Int) {
|
||||
* @param imageView View
|
||||
* @param placeholder 占位图
|
||||
*/
|
||||
fun ImageView.loadRoundedImage(
|
||||
fun ImageView.loadRoundedImage2(
|
||||
url: String?,
|
||||
roundingRadius: Float,
|
||||
@DrawableRes defaultResId: Int = 0,
|
||||
@@ -239,6 +239,32 @@ fun ImageView.loadRoundedImage(
|
||||
.into(this)
|
||||
}
|
||||
|
||||
fun ImageView.loadRoundedImage(
|
||||
any: Any,
|
||||
roundingRadius: Float,
|
||||
@DrawableRes defaultResId: Int = 0,
|
||||
isCenterCrop:Boolean = true
|
||||
) {
|
||||
//设置图片圆角角度
|
||||
val roundedCorners = RoundedCorners(dp2px(roundingRadius))
|
||||
val multiTransformation: MultiTransformation<Bitmap> = MultiTransformation(
|
||||
if (isCenterCrop) CenterCrop() else FitCenter(), roundedCorners
|
||||
)
|
||||
val options = RequestOptions
|
||||
.bitmapTransform(multiTransformation)
|
||||
if (defaultResId == 0) {
|
||||
options.placeholder(R.drawable.ic_default)
|
||||
.error(R.drawable.ic_default)
|
||||
} else {
|
||||
options.placeholder(defaultResId)
|
||||
.error(defaultResId)
|
||||
}
|
||||
Glide.with(context)
|
||||
.load(any)
|
||||
.apply(options)
|
||||
.into(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载图片到ImageView
|
||||
* @param imageUrl 图片地址
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.zmkg.coaloperation.ui.geologging
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.base.BaseVMBActivity
|
||||
import com.zmkg.coaloperation.base.viewmodel.TestViewModel
|
||||
import com.zmkg.coaloperation.databinding.ActivityAddGeoLoggingFaceBinding
|
||||
|
||||
class AddGeoLoggingFaceActivity :
|
||||
BaseVMBActivity<TestViewModel, ActivityAddGeoLoggingFaceBinding>(
|
||||
R.layout.activity_add_geo_logging_face
|
||||
) {
|
||||
|
||||
override fun initView(savedInstanceState: Bundle?) {
|
||||
mBinding.toolbarLay.title = "新增工作面"
|
||||
mBinding.btnAddFace.setOnClickListener {
|
||||
val faceName = mBinding.etWorkfaceName.text.toString().trim()
|
||||
if (faceName.isBlank()) {
|
||||
showToast("请输入工作面名称")
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun initData() {
|
||||
|
||||
}
|
||||
|
||||
override fun bindEvent() {
|
||||
|
||||
}
|
||||
|
||||
override fun processClick(v: View?) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+88
-19
@@ -1,19 +1,40 @@
|
||||
package com.zmkg.coaloperation.ui.geologging
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.core.view.marginLeft
|
||||
import androidx.core.view.marginStart
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.base.BaseVMBActivity
|
||||
import com.zmkg.coaloperation.base.viewmodel.TestViewModel
|
||||
import com.zmkg.coaloperation.databinding.ActivityAddGeologicalPointBinding
|
||||
import com.zmkg.coaloperation.utils.dp
|
||||
import com.zmkg.coaloperation.utils.useShapeSetBackground
|
||||
import net.lucode.hackware.magicindicator.FragmentContainerHelper
|
||||
import net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator
|
||||
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter
|
||||
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView
|
||||
import net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator
|
||||
import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.ColorTransitionPagerTitleView
|
||||
|
||||
/**
|
||||
* 地质编录点新增
|
||||
*/
|
||||
class AddGeologicalPointActivity :
|
||||
BaseVMBActivity<TestViewModel, ActivityAddGeologicalPointBinding>(R.layout.activity_add_geological_point) {
|
||||
BaseVMBActivity<TestViewModel, ActivityAddGeologicalPointBinding>(
|
||||
R.layout.activity_add_geological_point
|
||||
) {
|
||||
|
||||
|
||||
override fun initView(savedInstanceState: Bundle?) {
|
||||
@@ -21,13 +42,8 @@ class AddGeologicalPointActivity :
|
||||
// mBinding.toolbarLay.rightText = "新增工作面"
|
||||
|
||||
initFragments()
|
||||
//switchFragment(0)
|
||||
|
||||
supportFragmentManager.commit {
|
||||
setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
show(fragments[0])
|
||||
setReorderingAllowed(true)
|
||||
}
|
||||
initMagicIndicator()
|
||||
showFragment(0)
|
||||
}
|
||||
|
||||
override fun initData() {
|
||||
@@ -40,22 +56,75 @@ class AddGeologicalPointActivity :
|
||||
}
|
||||
|
||||
|
||||
private val fragments = mutableListOf<Fragment>()
|
||||
private val fragments = mutableListOf<AddGeologicalPointFragment>()
|
||||
private fun initFragments() {
|
||||
fragments.apply {
|
||||
add(AddGeologicalPointFragment())
|
||||
add(AddGeologicalPointFragment())
|
||||
add(AddGeologicalPointFragment())
|
||||
add(AddGeologicalPointFragment())
|
||||
add(AddGeologicalPointFragment())
|
||||
}
|
||||
titles.forEachIndexed { index, string ->
|
||||
val fragment = AddGeologicalPointFragment()
|
||||
fragments.add(fragment)
|
||||
supportFragmentManager.commit {
|
||||
// add(R.id.fragment_container, fragments[0], "volume").hide(fragments[0])
|
||||
replace(R.id.frgContainerView, fragments[0], "point01")
|
||||
add(R.id.frgContainerView, fragment, "frg_${index}").hide(fragment)
|
||||
// replace(R.id.frgContainerView, fragments[0], "point01")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchFragment(position: Int) {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
fragments[showFrgIndex].onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private val titles = listOf("1#", "2#", "3#", "4#", "5#", "6#")
|
||||
private fun initMagicIndicator() {
|
||||
val commonNavigator = CommonNavigator(this).apply {
|
||||
adapter = object : CommonNavigatorAdapter() {
|
||||
override fun getCount() = titles.size
|
||||
override fun getTitleView(context: Context?, index: Int): IPagerTitleView {
|
||||
return ColorTransitionPagerTitleView(context).apply {
|
||||
// setPadding(10.dp, 5.dp,10.dp, 5.dp)
|
||||
width = 65.dp
|
||||
text = titles[index]
|
||||
textSize = 20f
|
||||
normalColor = Color.WHITE
|
||||
selectedColor = "#30E0A1".toColorInt()
|
||||
useShapeSetBackground(
|
||||
cornerRadius = 12.dp.toFloat(),
|
||||
solidColor = "#1430E0A1".toColorInt(),
|
||||
strokeColor = "#0C0D0E".toColorInt(),
|
||||
strokeWidth = 8.dp
|
||||
)
|
||||
setOnClickListener { showFragment(index) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIndicator(context: Context?) = LinePagerIndicator(context).apply {
|
||||
// mode = LinePagerIndicator.MODE_WRAP_CONTENT
|
||||
// setColors("#30E0A1".toColorInt())
|
||||
// lineHeight = 1.dp.toFloat()
|
||||
//lineWidth = 10.dp.toFloat()
|
||||
// background = GradientDrawable().also {
|
||||
// it.shape = GradientDrawable.RECTANGLE
|
||||
// it.cornerRadius = 5.dp.toFloat()
|
||||
// it.setColor("#30E0A1".toColorInt())
|
||||
//// it.setStroke(strokeWidth, strokeColor)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
mBinding.magicIndicator.let {
|
||||
fragmentContainerHelper = FragmentContainerHelper(it)
|
||||
it.navigator = commonNavigator
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var fragmentContainerHelper:FragmentContainerHelper
|
||||
|
||||
private var showFrgIndex = 0
|
||||
private fun showFragment(position: Int) {
|
||||
showFrgIndex = position
|
||||
// supportFragmentManager.beginTransaction()
|
||||
// .replace(R.id.frgContainerView, fragments[position])
|
||||
// .commit()
|
||||
fragmentContainerHelper.handlePageSelected(position)
|
||||
supportFragmentManager.commit {
|
||||
setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
fragments.forEachIndexed { index, fragment ->
|
||||
|
||||
+33
-2
@@ -7,23 +7,54 @@ import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.adapter.GeoPointMultiAdapter
|
||||
import com.zmkg.coaloperation.base.BaseVMBFragment
|
||||
import com.zmkg.coaloperation.base.viewmodel.TestViewModel
|
||||
import com.zmkg.coaloperation.bean.BaseItem
|
||||
import com.zmkg.coaloperation.bean.CommonImageBean
|
||||
import com.zmkg.coaloperation.bean.GeoPointMultiItem
|
||||
import com.zmkg.coaloperation.databinding.FragmentAddGeologicalPointBinding
|
||||
|
||||
class AddGeologicalPointFragment :
|
||||
BaseVMBFragment<TestViewModel, FragmentAddGeologicalPointBinding>(R.layout.fragment_add_geological_point) {
|
||||
|
||||
companion object {
|
||||
const val IMAGE_REQUEST_CODE = 1000
|
||||
}
|
||||
|
||||
private val geoPointList: MutableList<GeoPointMultiItem> = mutableListOf()
|
||||
private val geoPointAdapter by lazy {
|
||||
GeoPointMultiAdapter(geoPointList)
|
||||
}
|
||||
|
||||
override fun initView(root: View?, savedInstanceState: Bundle?) {
|
||||
geoPointList.clear()
|
||||
val topList = mutableListOf(
|
||||
BaseItem(name = "点号", value = ""),
|
||||
BaseItem(name = "点距", value = ""),
|
||||
BaseItem(name = "累距", value = ""),
|
||||
)
|
||||
val bottomList = mutableListOf(
|
||||
BaseItem(name = "巷高", value = ""),
|
||||
BaseItem(name = "顶板", value = ""),
|
||||
BaseItem(name = "底板", value = ""),
|
||||
BaseItem(name = "巷顶煤厚", value = ""),
|
||||
)
|
||||
val imageList = mutableListOf<CommonImageBean>()
|
||||
imageList.add(CommonImageBean(isAddFlag = true))
|
||||
geoPointList.run {
|
||||
add(GeoPointMultiItem(itemType = GeoPointMultiItem.TYPE_BASE_INFO))
|
||||
add(
|
||||
GeoPointMultiItem(
|
||||
itemType = GeoPointMultiItem.TYPE_BASE_INFO,
|
||||
baseTopList = topList,
|
||||
baseBottomList = bottomList
|
||||
)
|
||||
)
|
||||
add(GeoPointMultiItem(itemType = GeoPointMultiItem.TYPE_LOGGING_INFO))
|
||||
add(GeoPointMultiItem(itemType = GeoPointMultiItem.TYPE_CALCULATE_INFO))
|
||||
add(GeoPointMultiItem(itemType = GeoPointMultiItem.TYPE_UPLOAD_IMAGE))
|
||||
add(
|
||||
GeoPointMultiItem(
|
||||
itemType = GeoPointMultiItem.TYPE_UPLOAD_IMAGE,
|
||||
images = imageList
|
||||
)
|
||||
)
|
||||
}
|
||||
mBinding.rvPointList.let {
|
||||
it.layoutManager = LinearLayoutManager(it.context)
|
||||
|
||||
+29
-1
@@ -2,10 +2,13 @@ package com.zmkg.coaloperation.ui.geologging
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.adapter.GeoLoggingFaceAdapter
|
||||
import com.zmkg.coaloperation.base.BaseVMBActivity
|
||||
import com.zmkg.coaloperation.base.viewmodel.TestViewModel
|
||||
import com.zmkg.coaloperation.databinding.ActivityGeologicalBinding
|
||||
import com.zmkg.coaloperation.db.entity.geologging.GeoLoggingFace
|
||||
|
||||
/**
|
||||
* 地质编录
|
||||
@@ -13,11 +16,36 @@ import com.zmkg.coaloperation.databinding.ActivityGeologicalBinding
|
||||
class GeologicalActivity :
|
||||
BaseVMBActivity<TestViewModel, ActivityGeologicalBinding>(R.layout.activity_geological) {
|
||||
|
||||
val faceList: MutableList<GeoLoggingFace> = mutableListOf()
|
||||
val faceAdapter by lazy {
|
||||
GeoLoggingFaceAdapter(faceList).apply {
|
||||
setOnItemClickListener { adapter, view, position ->
|
||||
toActivity(GeologicalPointActivity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initView(savedInstanceState: Bundle?) {
|
||||
mBinding.toolbarLay.title = "编录位置选择"
|
||||
mBinding.toolbarLay.rightText = "新增工作面"
|
||||
mBinding.toolbarLay.titleTvRight.setOnClickListener {
|
||||
toActivity(GeologicalPointActivity::class.java)
|
||||
toActivity(AddGeoLoggingFaceActivity::class.java)
|
||||
}
|
||||
|
||||
faceList.run {
|
||||
add(GeoLoggingFace(surfaceName = "5-20304回风工作面"))
|
||||
add(GeoLoggingFace(surfaceName = "5-20305回风工作面"))
|
||||
add(GeoLoggingFace(surfaceName = "5-20306回风工作面"))
|
||||
add(GeoLoggingFace(surfaceName = "5-20307回风工作面"))
|
||||
add(GeoLoggingFace(surfaceName = "5-20308回风工作面"))
|
||||
add(GeoLoggingFace(surfaceName = "5-20309回风工作面"))
|
||||
add(GeoLoggingFace(surfaceName = "5-20310回风工作面"))
|
||||
add(GeoLoggingFace(surfaceName = "5-20311回风工作面"))
|
||||
}
|
||||
|
||||
mBinding.rvGeoLogging.let {
|
||||
it.layoutManager = LinearLayoutManager(this)
|
||||
it.adapter = faceAdapter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+123
-123
@@ -1,123 +1,123 @@
|
||||
package com.zmkg.coaloperation.ui.report.adapter
|
||||
|
||||
import android.view.View
|
||||
import com.chad.library.adapter.base.module.LoadMoreModule
|
||||
import com.chad.library.adapter.base.viewholder.BaseDataBindingHolder
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.adapter.common.BaseDataBindingAdapter
|
||||
import com.zmkg.coaloperation.bean.ImageBean
|
||||
import com.zmkg.coaloperation.databinding.ItemRecycleImageBinding
|
||||
import com.zmkg.coaloperation.superfuntion.loadRoundedImage
|
||||
import java.io.File
|
||||
|
||||
class ImageAdapter(var maxNum: Int): BaseDataBindingAdapter<ImageBean, ItemRecycleImageBinding>(
|
||||
R.layout.item_recycle_image
|
||||
), LoadMoreModule {
|
||||
private var isEditModel = false
|
||||
private var editImageShow = false
|
||||
override fun bindViewClickListener(
|
||||
viewHolder: BaseDataBindingHolder<ItemRecycleImageBinding>,
|
||||
viewType: Int
|
||||
) {
|
||||
addChildClickViewIds(R.id.btn_delete)
|
||||
super.bindViewClickListener(viewHolder, viewType)
|
||||
}
|
||||
override fun convert(
|
||||
holder: BaseDataBindingHolder<ItemRecycleImageBinding>,
|
||||
item: ImageBean
|
||||
) {
|
||||
val mBinding = holder.dataBinding
|
||||
mBinding?.let {
|
||||
if(item.isAddButton){
|
||||
mBinding.ivImage.setImageResource(R.mipmap.upload)
|
||||
mBinding.btnDelete.visibility = View.GONE
|
||||
}else{
|
||||
if(item.isFilePath){
|
||||
mBinding.ivImage.loadRoundedImage(File(item.imageUrl), 5f)
|
||||
}else{
|
||||
mBinding.ivImage.loadRoundedImage(item.imageUrl, 5f, R.drawable.ic_default)
|
||||
}
|
||||
if(isEditModel){
|
||||
mBinding.btnDelete.visibility = View.VISIBLE
|
||||
}else{
|
||||
mBinding.btnDelete.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun isEditModel(isEdit: Boolean){
|
||||
isEditModel = isEdit
|
||||
notifyDataSetChanged()
|
||||
if(isEditModel && !editImageShow){
|
||||
showAddButton(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setList(list: Collection<ImageBean>?) {
|
||||
super.setList(list)
|
||||
if(isEditModel){
|
||||
showAddButton(true)
|
||||
}
|
||||
}
|
||||
override fun setNewInstance(list: MutableList<ImageBean>?) {
|
||||
super.setNewInstance(list)
|
||||
if(isEditModel){
|
||||
showAddButton(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addData(newData: Collection<ImageBean>) {
|
||||
if(isEditModel){
|
||||
if(editImageShow){
|
||||
var lastPosition = data.size - 1
|
||||
if(lastPosition < 0){
|
||||
lastPosition = 0
|
||||
}
|
||||
addData(lastPosition, newData)
|
||||
}else{
|
||||
showAddButton(true)
|
||||
}
|
||||
if(data.size > maxNum){
|
||||
showAddButton(false)
|
||||
}
|
||||
}else{
|
||||
super.addData(newData)
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeAt(position: Int) {
|
||||
super.removeAt(position)
|
||||
if(isEditModel && !editImageShow){
|
||||
showAddButton(true)
|
||||
}
|
||||
}
|
||||
fun getImageList(): MutableList<ImageBean>{
|
||||
var list = mutableListOf<ImageBean>()
|
||||
data.forEach { imageBean ->
|
||||
if(!imageBean.isAddButton){
|
||||
list.add(imageBean)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
fun getImageSize(): Int {
|
||||
if(isEditModel && editImageShow){
|
||||
return getDefItemCount() - 1
|
||||
}
|
||||
return getDefItemCount()
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示添加图片按钮
|
||||
* @param isShow true为显示 false为隐藏
|
||||
*/
|
||||
private fun showAddButton(isShow: Boolean){
|
||||
editImageShow = if(isShow){
|
||||
addData(ImageBean(isAddButton = true))
|
||||
true
|
||||
}else{
|
||||
removeAt(data.size-1)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
//package com.zmkg.coaloperation.ui.report.adapter
|
||||
//
|
||||
//import android.view.View
|
||||
//import com.chad.library.adapter.base.module.LoadMoreModule
|
||||
//import com.chad.library.adapter.base.viewholder.BaseDataBindingHolder
|
||||
//import com.zmkg.coaloperation.R
|
||||
//import com.zmkg.coaloperation.adapter.common.BaseDataBindingAdapter
|
||||
//import com.zmkg.coaloperation.bean.ImageBean
|
||||
//import com.zmkg.coaloperation.databinding.ItemRecycleImageBinding
|
||||
//import com.zmkg.coaloperation.superfuntion.loadRoundedImage
|
||||
//import java.io.File
|
||||
//
|
||||
//class ImageAdapter(var maxNum: Int): BaseDataBindingAdapter<ImageBean, ItemRecycleImageBinding>(
|
||||
// R.layout.item_recycle_image
|
||||
//), LoadMoreModule {
|
||||
// private var isEditModel = false
|
||||
// private var editImageShow = false
|
||||
// override fun bindViewClickListener(
|
||||
// viewHolder: BaseDataBindingHolder<ItemRecycleImageBinding>,
|
||||
// viewType: Int
|
||||
// ) {
|
||||
// addChildClickViewIds(R.id.btn_delete)
|
||||
// super.bindViewClickListener(viewHolder, viewType)
|
||||
// }
|
||||
// override fun convert(
|
||||
// holder: BaseDataBindingHolder<ItemRecycleImageBinding>,
|
||||
// item: ImageBean
|
||||
// ) {
|
||||
// val mBinding = holder.dataBinding
|
||||
// mBinding?.let {
|
||||
// if(item.isAddButton){
|
||||
// mBinding.ivImage.setImageResource(R.mipmap.upload)
|
||||
// mBinding.btnDelete.visibility = View.GONE
|
||||
// }else{
|
||||
// if(item.isFilePath){
|
||||
// mBinding.ivImage.loadRoundedImage(File(item.imageUrl), 5f)
|
||||
// }else{
|
||||
// mBinding.ivImage.loadRoundedImage(item.imageUrl, 5f, R.drawable.ic_default)
|
||||
// }
|
||||
// if(isEditModel){
|
||||
// mBinding.btnDelete.visibility = View.VISIBLE
|
||||
// }else{
|
||||
// mBinding.btnDelete.visibility = View.GONE
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// fun isEditModel(isEdit: Boolean){
|
||||
// isEditModel = isEdit
|
||||
// notifyDataSetChanged()
|
||||
// if(isEditModel && !editImageShow){
|
||||
// showAddButton(true)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun setList(list: Collection<ImageBean>?) {
|
||||
// super.setList(list)
|
||||
// if(isEditModel){
|
||||
// showAddButton(true)
|
||||
// }
|
||||
// }
|
||||
// override fun setNewInstance(list: MutableList<ImageBean>?) {
|
||||
// super.setNewInstance(list)
|
||||
// if(isEditModel){
|
||||
// showAddButton(true)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun addData(newData: Collection<ImageBean>) {
|
||||
// if(isEditModel){
|
||||
// if(editImageShow){
|
||||
// var lastPosition = data.size - 1
|
||||
// if(lastPosition < 0){
|
||||
// lastPosition = 0
|
||||
// }
|
||||
// addData(lastPosition, newData)
|
||||
// }else{
|
||||
// showAddButton(true)
|
||||
// }
|
||||
// if(data.size > maxNum){
|
||||
// showAddButton(false)
|
||||
// }
|
||||
// }else{
|
||||
// super.addData(newData)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun removeAt(position: Int) {
|
||||
// super.removeAt(position)
|
||||
// if(isEditModel && !editImageShow){
|
||||
// showAddButton(true)
|
||||
// }
|
||||
// }
|
||||
// fun getImageList(): MutableList<ImageBean>{
|
||||
// var list = mutableListOf<ImageBean>()
|
||||
// data.forEach { imageBean ->
|
||||
// if(!imageBean.isAddButton){
|
||||
// list.add(imageBean)
|
||||
// }
|
||||
// }
|
||||
// return list
|
||||
// }
|
||||
// fun getImageSize(): Int {
|
||||
// if(isEditModel && editImageShow){
|
||||
// return getDefItemCount() - 1
|
||||
// }
|
||||
// return getDefItemCount()
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 显示添加图片按钮
|
||||
// * @param isShow true为显示 false为隐藏
|
||||
// */
|
||||
// private fun showAddButton(isShow: Boolean){
|
||||
// editImageShow = if(isShow){
|
||||
// addData(ImageBean(isAddButton = true))
|
||||
// true
|
||||
// }else{
|
||||
// removeAt(data.size-1)
|
||||
// false
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.zmkg.coaloperation.ui.report.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.adapter.ReportAdapter
|
||||
import com.zmkg.coaloperation.base.BaseVMBFragment
|
||||
import com.zmkg.coaloperation.bean.CommonImageBean
|
||||
import com.zmkg.coaloperation.bean.ReportItem
|
||||
import com.zmkg.coaloperation.databinding.FragmentReport2Binding
|
||||
import com.zmkg.coaloperation.ui.report.viewmodel.ReportViewModel
|
||||
import com.zmkg.coaloperation.utils.dp
|
||||
import com.zmkg.coaloperation.utils.gone
|
||||
|
||||
class Report2Fragment :
|
||||
BaseVMBFragment<ReportViewModel, FragmentReport2Binding>(R.layout.fragment_report2) {
|
||||
|
||||
lateinit var pageStatus: PageStatus
|
||||
|
||||
enum class PageStatus(val status: Int) {
|
||||
ADD(1),
|
||||
EDIT(2),
|
||||
SEE(3),
|
||||
NO_EDIT(4)
|
||||
}
|
||||
|
||||
private val reportItemList = mutableListOf(
|
||||
ReportItem(viewType = 2, name = "上报类型"),
|
||||
ReportItem(viewType = 2, name = "上报区域"),
|
||||
ReportItem(viewType = 1, name = "上报地点", isSingleLine = true, inputHint = "请输入"),
|
||||
ReportItem(viewType = 1, name = "上报描述", isSingleLine = true, inputHint = "请输入"),
|
||||
ReportItem(viewType = 3, name = "上报时间"),
|
||||
ReportItem(viewType = 2, name = "处理状态"),
|
||||
ReportItem(viewType = 2, name = "上报人"),
|
||||
ReportItem(viewType = 2, name = "处理人"),
|
||||
ReportItem(viewType = 1, name = "处理策略", isSingleLine = false, inputShowLines = 4, inputHint = "请输入"),
|
||||
ReportItem(viewType = 1, name = "处理结果", isSingleLine = false, inputShowLines = 4, inputHint = "请输入"),
|
||||
ReportItem(viewType = 3, name = "处理时间"),
|
||||
ReportItem(viewType = 4, name = "上传图片", images = mutableListOf(
|
||||
CommonImageBean(isAddFlag = true)
|
||||
)),
|
||||
ReportItem(viewType = 1, name = "备注", isSingleLine = false, inputShowLines = 6 ,inputHint = "请输入备注信息,120字以内"),
|
||||
)
|
||||
private val reportAdapter by lazy {
|
||||
ReportAdapter(reportItemList)
|
||||
}
|
||||
|
||||
override fun initView(root: View?, savedInstanceState: Bundle?) {
|
||||
mBinding.toolbarLay.title = "新增临时上报"
|
||||
mBinding.toolbarLay.rightText = "查看记录"
|
||||
mBinding.toolbarLay.titleIvBack.gone()
|
||||
mBinding.toolbarLay.titleTvName.updateLayoutParams<RelativeLayout.LayoutParams> {
|
||||
leftMargin = 20.dp
|
||||
}
|
||||
mBinding.rvReport.let {
|
||||
it.layoutManager = LinearLayoutManager(it.context)
|
||||
it.adapter = reportAdapter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun bindEvent() {
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
}
|
||||
|
||||
override fun transparentStatusBar(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun createObserve() {
|
||||
super.createObserve()
|
||||
}
|
||||
|
||||
}
|
||||
+203
-199
@@ -1,200 +1,204 @@
|
||||
package com.zmkg.coaloperation.ui.report.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.listener.OnItemChildClickListener
|
||||
import com.chad.library.adapter.base.listener.OnItemClickListener
|
||||
import com.luck.picture.lib.basic.PictureSelectionModel
|
||||
import com.luck.picture.lib.basic.PictureSelector
|
||||
import com.luck.picture.lib.config.SelectMimeType
|
||||
import com.luck.picture.lib.config.SelectModeConfig
|
||||
import com.luck.picture.lib.entity.LocalMedia
|
||||
import com.luck.picture.lib.interfaces.OnResultCallbackListener
|
||||
import com.luck.picture.lib.utils.SandboxTransformUtils
|
||||
import com.zmkg.coaloperation.utils.pictureSelector.ImageFileCompressEngine
|
||||
import com.zmkg.coaloperation.R
|
||||
import com.zmkg.coaloperation.base.BaseVMBFragment
|
||||
import com.zmkg.coaloperation.bean.ImageBean
|
||||
import com.zmkg.coaloperation.databinding.FragmentReportBinding
|
||||
import com.zmkg.coaloperation.ui.report.adapter.ImageAdapter
|
||||
import com.zmkg.coaloperation.ui.report.viewmodel.ReportViewModel
|
||||
import com.zmkg.coaloperation.utils.pictureSelector.GlideEngine
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ReportFragment :
|
||||
BaseVMBFragment<ReportViewModel, FragmentReportBinding>(R.layout.fragment_report),
|
||||
OnItemClickListener, OnItemChildClickListener {
|
||||
|
||||
private val imageAdapter by lazy { ImageAdapter(maxImageNum) }
|
||||
lateinit var pageStatus: PageStatus
|
||||
|
||||
companion object {
|
||||
const val maxImageNum = 5
|
||||
}
|
||||
|
||||
override fun initView(root: View?, savedInstanceState: Bundle?) {
|
||||
|
||||
mBinding.apply {
|
||||
val gridLayoutManager = GridLayoutManager(context, 3)
|
||||
layInspectionReport.rvList.layoutManager = gridLayoutManager
|
||||
imageAdapter.setOnItemClickListener(this@ReportFragment)
|
||||
imageAdapter.setOnItemChildClickListener(this@ReportFragment)
|
||||
mBinding.layInspectionReport.rvList.adapter = imageAdapter
|
||||
ViewCompat.setNestedScrollingEnabled(layInspectionReport.rvList, false)
|
||||
}
|
||||
|
||||
setPageModel(PageStatus.ADD)
|
||||
|
||||
}
|
||||
|
||||
enum class PageStatus(val status: Int) {
|
||||
ADD(1),
|
||||
EDIT(2),
|
||||
SEE(3),
|
||||
NO_EDIT(4)
|
||||
}
|
||||
|
||||
private fun setPageModel(status: PageStatus) {
|
||||
this.pageStatus = status
|
||||
mBinding?.apply {
|
||||
when (status) {
|
||||
PageStatus.ADD -> {
|
||||
lifecycleScope.launch {
|
||||
mViewModel.noEdit.emit(false)
|
||||
}
|
||||
}
|
||||
|
||||
PageStatus.EDIT -> {
|
||||
lifecycleScope.launch {
|
||||
mViewModel.noEdit.emit(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PageStatus.SEE -> {
|
||||
lifecycleScope.launch {
|
||||
mViewModel.noEdit.emit(true)
|
||||
}
|
||||
}
|
||||
|
||||
PageStatus.NO_EDIT -> {
|
||||
lifecycleScope.launch {
|
||||
mViewModel.noEdit.emit(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun bindEvent() {
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
}
|
||||
|
||||
override fun transparentStatusBar(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun createObserve() {
|
||||
super.createObserve()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
mViewModel.noEdit.collectLatest { noEdit ->
|
||||
mBinding.apply {
|
||||
imageAdapter.isEditModel(!noEdit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
|
||||
val imageBean: ImageBean = adapter.getItem(position) as ImageBean
|
||||
imageBean?.let { it ->
|
||||
if (it.isAddButton) {
|
||||
initPictureSelectorModel()
|
||||
.forResult(object : OnResultCallbackListener<LocalMedia?> {
|
||||
override fun onResult(result: ArrayList<LocalMedia?>?) {
|
||||
result?.let { list ->
|
||||
var resultList = localMediaToImageList(list)
|
||||
imageAdapter.addData(resultList)
|
||||
setImageNum(imageAdapter.getImageSize())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
})
|
||||
} else {
|
||||
// startFullScreenImageActivity(
|
||||
// it.isFilePath,
|
||||
// it.imageUrl
|
||||
//package com.zmkg.coaloperation.ui.report.fragment
|
||||
//
|
||||
//import android.os.Bundle
|
||||
//import android.view.View
|
||||
//import androidx.core.view.ViewCompat
|
||||
//import androidx.lifecycle.Lifecycle
|
||||
//import androidx.lifecycle.lifecycleScope
|
||||
//import androidx.lifecycle.repeatOnLifecycle
|
||||
//import androidx.recyclerview.widget.GridLayoutManager
|
||||
//import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
//import com.chad.library.adapter.base.listener.OnItemChildClickListener
|
||||
//import com.chad.library.adapter.base.listener.OnItemClickListener
|
||||
//import com.luck.picture.lib.basic.PictureSelectionModel
|
||||
//import com.luck.picture.lib.basic.PictureSelector
|
||||
//import com.luck.picture.lib.config.SelectMimeType
|
||||
//import com.luck.picture.lib.config.SelectModeConfig
|
||||
//import com.luck.picture.lib.entity.LocalMedia
|
||||
//import com.luck.picture.lib.interfaces.OnResultCallbackListener
|
||||
//import com.luck.picture.lib.utils.SandboxTransformUtils
|
||||
//import com.zmkg.coaloperation.utils.pictureSelector.ImageFileCompressEngine
|
||||
//import com.zmkg.coaloperation.R
|
||||
//import com.zmkg.coaloperation.base.BaseActivity
|
||||
//import com.zmkg.coaloperation.base.BaseVMBFragment
|
||||
//import com.zmkg.coaloperation.bean.ImageBean
|
||||
//import com.zmkg.coaloperation.databinding.FragmentReportBinding
|
||||
//import com.zmkg.coaloperation.ui.report.adapter.ImageAdapter
|
||||
//import com.zmkg.coaloperation.ui.report.viewmodel.ReportViewModel
|
||||
//import com.zmkg.coaloperation.utils.permission.StorageUtils
|
||||
//import com.zmkg.coaloperation.utils.pictureSelector.GlideEngine
|
||||
//import kotlinx.coroutines.flow.collectLatest
|
||||
//import kotlinx.coroutines.launch
|
||||
//
|
||||
//class ReportFragment :
|
||||
// BaseVMBFragment<ReportViewModel, FragmentReportBinding>(R.layout.fragment_report),
|
||||
// OnItemClickListener, OnItemChildClickListener {
|
||||
//
|
||||
// private val imageAdapter by lazy { ImageAdapter(maxImageNum) }
|
||||
// lateinit var pageStatus: PageStatus
|
||||
//
|
||||
// companion object {
|
||||
// const val maxImageNum = 5
|
||||
// }
|
||||
//
|
||||
// override fun initView(root: View?, savedInstanceState: Bundle?) {
|
||||
//
|
||||
// mBinding.apply {
|
||||
// val gridLayoutManager = GridLayoutManager(context, 3)
|
||||
// layInspectionReport.rvList.layoutManager = gridLayoutManager
|
||||
// imageAdapter.setOnItemClickListener(this@ReportFragment)
|
||||
// imageAdapter.setOnItemChildClickListener(this@ReportFragment)
|
||||
// mBinding.layInspectionReport.rvList.adapter = imageAdapter
|
||||
// ViewCompat.setNestedScrollingEnabled(layInspectionReport.rvList, false)
|
||||
// }
|
||||
//
|
||||
// setPageModel(PageStatus.ADD)
|
||||
//
|
||||
// }
|
||||
//
|
||||
// enum class PageStatus(val status: Int) {
|
||||
// ADD(1),
|
||||
// EDIT(2),
|
||||
// SEE(3),
|
||||
// NO_EDIT(4)
|
||||
// }
|
||||
//
|
||||
// private fun setPageModel(status: PageStatus) {
|
||||
// this.pageStatus = status
|
||||
// mBinding?.apply {
|
||||
// when (status) {
|
||||
// PageStatus.ADD -> {
|
||||
// lifecycleScope.launch {
|
||||
// mViewModel.noEdit.emit(false)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// PageStatus.EDIT -> {
|
||||
// lifecycleScope.launch {
|
||||
// mViewModel.noEdit.emit(false)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// PageStatus.SEE -> {
|
||||
// lifecycleScope.launch {
|
||||
// mViewModel.noEdit.emit(true)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// PageStatus.NO_EDIT -> {
|
||||
// lifecycleScope.launch {
|
||||
// mViewModel.noEdit.emit(true)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// override fun bindEvent() {
|
||||
// }
|
||||
//
|
||||
// override fun onClick(v: View?) {
|
||||
// }
|
||||
//
|
||||
// override fun transparentStatusBar(): Boolean {
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// override fun createObserve() {
|
||||
// super.createObserve()
|
||||
// lifecycleScope.launch {
|
||||
// repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
// mViewModel.noEdit.collectLatest { noEdit ->
|
||||
// mBinding.apply {
|
||||
// imageAdapter.isEditModel(!noEdit)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
|
||||
// val imageBean: ImageBean = adapter.getItem(position) as ImageBean
|
||||
// imageBean?.let { it ->
|
||||
// if (it.isAddButton) {
|
||||
// StorageUtils.requestPermission(context as BaseActivity) {
|
||||
// initPictureSelectorModel()
|
||||
// .forResult(object : OnResultCallbackListener<LocalMedia?> {
|
||||
// override fun onResult(result: ArrayList<LocalMedia?>?) {
|
||||
// result?.let { list ->
|
||||
// var resultList = localMediaToImageList(list)
|
||||
// imageAdapter.addData(resultList)
|
||||
// setImageNum(imageAdapter.getImageSize())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onCancel() {}
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
//// startFullScreenImageActivity(
|
||||
//// it.isFilePath,
|
||||
//// it.imageUrl
|
||||
//// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun setImageNum(size: Int) {
|
||||
// mBinding.layInspectionReport.tvImageNum.text = getString(
|
||||
// R.string.image_list_num,
|
||||
// size,
|
||||
// maxImageNum
|
||||
// )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setImageNum(size: Int) {
|
||||
mBinding.layInspectionReport.tvImageNum.text = getString(
|
||||
R.string.image_list_num,
|
||||
size,
|
||||
maxImageNum
|
||||
)
|
||||
}
|
||||
|
||||
private fun initPictureSelectorModel(): PictureSelectionModel {
|
||||
return PictureSelector.create(this)
|
||||
.openGallery(SelectMimeType.TYPE_IMAGE)
|
||||
.setSelectionMode(SelectModeConfig.MULTIPLE)
|
||||
.setMaxSelectNum(maxImageNum - imageAdapter.getImageSize())
|
||||
.setCompressEngine(ImageFileCompressEngine())
|
||||
.setImageEngine(GlideEngine.createGlideEngine())
|
||||
.setSandboxFileEngine { context, srcPath, mineType, call ->
|
||||
if (call != null) {
|
||||
var sandboxPath =
|
||||
SandboxTransformUtils.copyPathToSandbox(context, srcPath, mineType)
|
||||
call.onCallback(srcPath, sandboxPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
override fun onItemChildClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
|
||||
adapter.removeAt(position)
|
||||
}
|
||||
|
||||
fun localMediaToImageList(result: ArrayList<LocalMedia?>): MutableList<ImageBean> {
|
||||
var imageList = mutableListOf<ImageBean>()
|
||||
var imageBean: ImageBean
|
||||
result.forEach {
|
||||
it?.let {
|
||||
imageBean = ImageBean(getImagePath(it), true)
|
||||
imageList.add(imageBean)
|
||||
}
|
||||
}
|
||||
return imageList
|
||||
}
|
||||
|
||||
private fun getImagePath(localMedia: LocalMedia): String {
|
||||
if (!localMedia.cutPath.isNullOrEmpty()) {
|
||||
return localMedia.cutPath
|
||||
}
|
||||
if (!localMedia.compressPath.isNullOrEmpty()) {
|
||||
return localMedia.compressPath
|
||||
}
|
||||
if (!localMedia.sandboxPath.isNullOrEmpty()) {
|
||||
return localMedia.sandboxPath
|
||||
}
|
||||
return localMedia.path
|
||||
}
|
||||
}
|
||||
// }
|
||||
//
|
||||
// private fun initPictureSelectorModel(): PictureSelectionModel {
|
||||
// return PictureSelector.create(this)
|
||||
// .openGallery(SelectMimeType.TYPE_IMAGE)
|
||||
// .setSelectionMode(SelectModeConfig.MULTIPLE)
|
||||
// .setMaxSelectNum(maxImageNum - imageAdapter.getImageSize())
|
||||
// .setCompressEngine(ImageFileCompressEngine())
|
||||
// .setImageEngine(GlideEngine.createGlideEngine())
|
||||
// .setSandboxFileEngine { context, srcPath, mineType, call ->
|
||||
// if (call != null) {
|
||||
// var sandboxPath =
|
||||
// SandboxTransformUtils.copyPathToSandbox(context, srcPath, mineType)
|
||||
// call.onCallback(srcPath, sandboxPath)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// override fun onItemChildClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
|
||||
// adapter.removeAt(position)
|
||||
// }
|
||||
//
|
||||
// fun localMediaToImageList(result: ArrayList<LocalMedia?>): MutableList<ImageBean> {
|
||||
// var imageList = mutableListOf<ImageBean>()
|
||||
// var imageBean: ImageBean
|
||||
// result.forEach {
|
||||
// it?.let {
|
||||
// imageBean = ImageBean(getImagePath(it), true)
|
||||
// imageList.add(imageBean)
|
||||
// }
|
||||
// }
|
||||
// return imageList
|
||||
// }
|
||||
//
|
||||
// private fun getImagePath(localMedia: LocalMedia): String {
|
||||
// if (!localMedia.cutPath.isNullOrEmpty()) {
|
||||
// return localMedia.cutPath
|
||||
// }
|
||||
// if (!localMedia.compressPath.isNullOrEmpty()) {
|
||||
// return localMedia.compressPath
|
||||
// }
|
||||
// if (!localMedia.sandboxPath.isNullOrEmpty()) {
|
||||
// return localMedia.sandboxPath
|
||||
// }
|
||||
// return localMedia.path
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -7,6 +7,8 @@ import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.bumptech.glide.Glide
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -126,4 +128,8 @@ fun View.useShapeSetBackground(
|
||||
this.background = shape
|
||||
}
|
||||
|
||||
fun ImageView.load(any:Any) {
|
||||
Glide.with(this.context).load(any).into(this)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.zmkg.coaloperation.utils
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import com.zhihu.matisse.Matisse
|
||||
import com.zhihu.matisse.MimeType
|
||||
import com.zhihu.matisse.R
|
||||
import com.zhihu.matisse.engine.impl.GlideEngine
|
||||
import com.zhihu.matisse.internal.entity.CaptureStrategy
|
||||
import com.zmkg.coaloperation.base.BaseActivity
|
||||
|
||||
object GalleryUtils {
|
||||
|
||||
fun openGallery(activity: BaseActivity, action:(Intent)-> Unit) {
|
||||
Matisse.from(activity)
|
||||
.choose(MimeType.ofImage(), false) // 只选图片
|
||||
.capture(true)
|
||||
.captureStrategy(
|
||||
CaptureStrategy(
|
||||
true,
|
||||
"${activity.packageName}.fileprovider",
|
||||
"coalImage"
|
||||
)
|
||||
)
|
||||
.countable(true) // 显示选中数量
|
||||
.maxSelectable(9) // 最多选9张
|
||||
.gridExpectedSize(120.dp)
|
||||
.restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
||||
.thumbnailScale(0.8f)
|
||||
.theme(R.style.Matisse_Dracula)
|
||||
.imageEngine(GlideEngine()) // 必须设置引擎
|
||||
.forResultLaunch { intent ->
|
||||
activity.launchActivityResult(intent) { result ->
|
||||
result.data?.let {
|
||||
action(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.zmkg.coaloperation.utils.permission//package com.dheaven.mscapp.SIGFFCNFN.permission
|
||||
//
|
||||
//import androidx.activity.result.contract.ActivityResultContracts
|
||||
//import androidx.fragment.app.Fragment
|
||||
//
|
||||
//class InvisibleFragment : Fragment() {
|
||||
//
|
||||
// private var singlePermissionAction: ((Boolean) -> Unit)? = null
|
||||
// private var multiplePermissionAction: ((MutableMap<String,Boolean>) -> Unit)? = null
|
||||
//
|
||||
// private val singlePermissionLauncher = registerForActivityResult(
|
||||
// ActivityResultContracts.RequestPermission()
|
||||
// ) {
|
||||
// singlePermissionAction?.invoke(it)
|
||||
// }
|
||||
// private val multiplePermissionLauncher = registerForActivityResult(
|
||||
// ActivityResultContracts.RequestMultiplePermissions()
|
||||
// ) {
|
||||
// multiplePermissionAction?.invoke(it)
|
||||
// }
|
||||
//
|
||||
// private fun requestSinglePermission(permission: String, action: (Boolean) -> Unit) {
|
||||
// singlePermissionAction = action
|
||||
// singlePermissionLauncher.launch(permission)
|
||||
// }
|
||||
// private fun requestMultiplePermission(permission: Array<String>, action: (MutableMap<String,Boolean>) -> Unit) {
|
||||
// multiplePermissionAction = action
|
||||
// multiplePermissionLauncher.launch(permission)
|
||||
// }
|
||||
// fun requestNow(
|
||||
// callback: (granted: Boolean, deniedList: List<String>) -> Unit,
|
||||
// args: Array<String>
|
||||
// ) {
|
||||
// permissionCallback = callback
|
||||
//// requestPermissions(args, 1)
|
||||
//
|
||||
// if (args.size == 1) {
|
||||
// requestSinglePermission(args[0]) {
|
||||
// val deniedList = mutableListOf<String>()
|
||||
// if (it.not()) {
|
||||
// deniedList.add(args[0])
|
||||
// }
|
||||
// callback(it, deniedList)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// requestMultiplePermission(args){
|
||||
// val deniedList = mutableListOf<String>()
|
||||
// it.entries.forEach { entry ->
|
||||
// entry.apply {
|
||||
// if (value.not()) {
|
||||
// deniedList.add(key)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// val granted = deniedList.isEmpty()
|
||||
// callback(granted, deniedList)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//// override fun onRequestPermissionsResult(
|
||||
//// requestCode: Int,
|
||||
//// permissions: Array<String>,
|
||||
//// grantResults: IntArray
|
||||
//// ) {
|
||||
//// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
//// if (requestCode == 1) {
|
||||
//// val deniedList: MutableList<String> = ArrayList()
|
||||
//// for (i in grantResults.indices) {
|
||||
//// if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
|
||||
//// deniedList.add(permissions[i])
|
||||
//// }
|
||||
//// }
|
||||
//// val allGranted = deniedList.isEmpty()
|
||||
//// permissionCallback?.invoke(allGranted, deniedList)
|
||||
//// }
|
||||
//// }
|
||||
//
|
||||
// private var permissionCallback: ((granted: Boolean, deniedList: List<String>) -> Unit)? = null
|
||||
//}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.dheaven.mscapp.SIGFFCNFN.permission
|
||||
|
||||
import com.zmkg.coaloperation.base.BaseActivity
|
||||
|
||||
object PermissionUtils {
|
||||
// private const val TAG = "InvisibleFragment"
|
||||
fun request(
|
||||
activity: BaseActivity,
|
||||
permissions: Array<String>,
|
||||
callback: (granted: Boolean, deniedList: List<String>) -> Unit
|
||||
) {
|
||||
if (permissions.isEmpty()) {
|
||||
return
|
||||
}
|
||||
// val fragmentManager = activity.supportFragmentManager
|
||||
// val fragment = fragmentManager.findFragmentByTag(TAG)
|
||||
// var invisibleFragment: InvisibleFragment? = null
|
||||
// if (invisibleFragment == null) {
|
||||
// invisibleFragment = InvisibleFragment()
|
||||
// fragmentManager.beginTransaction().add(invisibleFragment, TAG).commitNow()
|
||||
// } else {
|
||||
// invisibleFragment = fragment as InvisibleFragment
|
||||
// }
|
||||
// invisibleFragment.requestNow(callback, permissions)
|
||||
//单权限请求
|
||||
if (permissions.size == 1) {
|
||||
val permission = permissions[0]
|
||||
activity.requestSinglePermissionResult(permission) {
|
||||
val deniedList = mutableListOf<String>()
|
||||
if (it.not()) {
|
||||
deniedList.add(permission)
|
||||
}
|
||||
callback(it, deniedList)
|
||||
}
|
||||
return
|
||||
}
|
||||
//多权限请求
|
||||
activity.requestMultiplePermissionsResult(permissions) {
|
||||
val deniedList = mutableListOf<String>()
|
||||
it.entries.forEach { entry ->
|
||||
entry.apply {
|
||||
if (value.not()) {
|
||||
deniedList.add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
val granted = deniedList.isEmpty()
|
||||
callback(granted, deniedList)
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.zmkg.coaloperation.utils.permission
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import com.zmkg.coaloperation.base.BaseActivity
|
||||
|
||||
object StorageUtils {
|
||||
|
||||
fun requestPermission(activity: BaseActivity, callback: () -> Unit) {
|
||||
activity.requestSinglePermissionResult(Manifest.permission.CAMERA) {
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// if (Environment.isExternalStorageManager()) {
|
||||
// callback()
|
||||
// } else {
|
||||
// val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
//// activity.startActivityForResult(intent, MANAGE_REQUEST_CODE)
|
||||
// activity.launchActivityResult(intent) {
|
||||
// callback()
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
activity.requestSinglePermissionResult(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
|
||||
callback()
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 699 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black_bg"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar_lay"
|
||||
layout="@layout/lay_title_right_bar" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:background="@drawable/bg_stroke_gray"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="工作面名称"
|
||||
android:textColor="#6A6F87"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etWorkfaceName"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:background="@drawable/rectangle_stroke_grey_corner8"
|
||||
android:hint="请输入"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textColorHint="#757B8C"
|
||||
android:textCursorDrawable="@drawable/cursor_white"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.allen.library.shape.ShapeButton
|
||||
android:id="@+id/btnAddFace"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="35dp"
|
||||
android:text="确认新增"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
app:shapeCornersRadius="36dp"
|
||||
app:shapeSolidColor="#21A69A" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
@@ -37,77 +37,9 @@
|
||||
app:shapeCornersRadius="36dp"
|
||||
app:shapeSolidColor="#21A69A" />
|
||||
|
||||
<LinearLayout
|
||||
<net.lucode.hackware.magicindicator.MagicIndicator
|
||||
android:id="@+id/magicIndicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.allen.library.shape.ShapeTextView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:text="1#"
|
||||
android:textColor="#30E0A1"
|
||||
android:textSize="20sp"
|
||||
app:shapeCornersRadius="8dp"
|
||||
app:shapeSolidColor="#1430E0A1" />
|
||||
|
||||
<com.allen.library.shape.ShapeTextView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:text="1#"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
app:shapeCornersRadius="8dp"
|
||||
app:shapeSolidColor="#1430E0A1" />
|
||||
|
||||
<com.allen.library.shape.ShapeTextView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:text="1#"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
app:shapeCornersRadius="8dp"
|
||||
app:shapeSolidColor="#1430E0A1" />
|
||||
|
||||
<com.allen.library.shape.ShapeTextView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:text="1#"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
app:shapeCornersRadius="8dp"
|
||||
app:shapeSolidColor="#1430E0A1" />
|
||||
|
||||
<com.allen.library.shape.ShapeTextView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:text="1#"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
app:shapeCornersRadius="8dp"
|
||||
app:shapeSolidColor="#1430E0A1" />
|
||||
|
||||
<com.allen.library.shape.ShapeTextView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:text="1#"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
app:shapeCornersRadius="8dp"
|
||||
app:shapeSolidColor="#1430E0A1" />
|
||||
</LinearLayout>
|
||||
android:layout_height="56dp"/>
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
@@ -21,6 +21,7 @@
|
||||
android:id="@+id/rvGeoLogging"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:overScrollMode="never"
|
||||
tools:listitem="@layout/item_geological_list" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -38,25 +38,25 @@
|
||||
android:paddingBottom="20dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRecord"
|
||||
android:id="@+id/btnStartLogging"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_btn_save"
|
||||
android:text="查看记录"
|
||||
android:text="开始编录"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSubmit"
|
||||
android:id="@+id/btnCompleteLogging"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_btn_export"
|
||||
android:text="确认提交"
|
||||
android:text="编录完成"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<com.allen.library.shape.ShapeLinearLayout
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:shapeCornersRadius="30dp"
|
||||
app:shapeSolidColor="#17181C"
|
||||
app:shapeStrokeColor="#282C38"
|
||||
app:shapeStrokeWidth="1dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDialogTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="50dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="保存" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDialogContent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textColor="#6A6F87"
|
||||
android:textSize="14sp"
|
||||
tools:text="是否确认保存输入的消息" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginBottom="50dp"
|
||||
android:layout_marginTop="30dp">
|
||||
|
||||
<com.allen.library.shape.ShapeButton
|
||||
android:id="@+id/btnCancel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"
|
||||
app:shapeCornersRadius="22dp"
|
||||
tools:text="取消"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
app:shapeSolidColor="#353846" />
|
||||
|
||||
<com.allen.library.shape.ShapeButton
|
||||
android:id="@+id/btnConfirm"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"
|
||||
tools:text="确定"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
app:shapeCornersRadius="22dp"
|
||||
app:shapeSolidColor="#21A69A" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.allen.library.shape.ShapeLinearLayout>
|
||||
</layout>
|
||||
@@ -28,14 +28,13 @@
|
||||
android:layout_marginBottom="70dp">
|
||||
|
||||
|
||||
<com.allen.library.shape.ShapeLinearLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="vertical"
|
||||
app:shapeCornersRadius="6dp"
|
||||
app:shapeSolidColor="@color/theme_color">
|
||||
android:background="@drawable/bg_stroke_gray">
|
||||
|
||||
<com.zmkg.coaloperation.view.CustomTextSelectView
|
||||
android:id="@+id/type"
|
||||
@@ -153,7 +152,7 @@
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</com.allen.library.shape.ShapeLinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black_bg"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar_lay"
|
||||
layout="@layout/lay_title_right_bar"
|
||||
app:goneBack="@{true}"
|
||||
app:title="@{@string/title_report}"
|
||||
app:transparentBg="@{true}" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvReport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:overScrollMode="never"
|
||||
tools:listitem="@layout/list_item_report"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="10"
|
||||
android:background="@drawable/bg_stroke_gray"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.allen.library.shape.ShapeButton
|
||||
android:id="@+id/btnConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginHorizontal="45dp"
|
||||
android:layout_marginVertical="20dp"
|
||||
android:gravity="center"
|
||||
android:text="确认"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
app:shapeCornersRadius="20dp"
|
||||
app:shapeSolidColor="@color/text_green" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
@@ -7,83 +7,35 @@
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.allen.library.shape.ShapeRelativeLayout
|
||||
<com.allen.library.shape.ShapeLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="74dp"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
app:shapeCornersRadius="6dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:gravity="center_vertical"
|
||||
app:shapeSolidColor="#1521A69A"
|
||||
app:shapeStrokeColor="#10514C"
|
||||
app:shapeStrokeColor="#282C38"
|
||||
app:shapeStrokeWidth="1dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="13dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:background="@drawable/ic_geological" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/tvFaceName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="60dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
tools:text="5-20304回风工作面" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/expand"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:padding="6dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="13dp"
|
||||
android:src="@drawable/ic_arrow_right_gray"
|
||||
android:tag="0" />
|
||||
|
||||
|
||||
</com.allen.library.shape.ShapeRelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/record_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="200dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/item_geological_list_content" />
|
||||
|
||||
<com.allen.library.shape.ShapeTextView
|
||||
android:id="@+id/start_record"
|
||||
android:layout_width="154dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="开始记录"
|
||||
android:textColor="@color/white"
|
||||
app:shapeCornersRadius="30dp"
|
||||
app:shapeSolidColor="#21A69A" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</com.allen.library.shape.ShapeLinearLayout>
|
||||
|
||||
</layout>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:id="@+id/ivImage"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_add_image"/>
|
||||
</FrameLayout>
|
||||
</layout>
|
||||
@@ -25,18 +25,22 @@
|
||||
android:layout_marginStart="20dp"
|
||||
tools:text="瓦斯含量" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvItemValue"
|
||||
<EditText
|
||||
android:id="@+id/etInputValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:minWidth="80dp"
|
||||
android:gravity="end|center_vertical"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="15m/s" />
|
||||
android:hint="请输入"
|
||||
android:background="@color/transparent"
|
||||
android:textCursorDrawable="@drawable/cursor_white"
|
||||
android:textColorHint="#757B8C"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:id="@+id/divider"
|
||||
|
||||
@@ -38,16 +38,19 @@
|
||||
android:layout_gravity="bottom"
|
||||
app:dividerColor="#20222B" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvItemValue"
|
||||
<EditText
|
||||
android:id="@+id/etInputValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:background="@color/transparent"
|
||||
android:textCursorDrawable="@drawable/cursor_white"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
tools:text="15m/s" />
|
||||
android:hint="请输入"
|
||||
android:textColorHint="#757B8C"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -62,13 +62,14 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="15dp"
|
||||
android:background="@drawable/bottom_border"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView
|
||||
@@ -93,14 +94,17 @@
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1024.341" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
app:dividerColor="#20222B" />
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView
|
||||
@@ -133,8 +137,8 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="15dp"
|
||||
android:background="@drawable/bottom_border"
|
||||
android:orientation="horizontal">
|
||||
|
||||
@@ -168,10 +172,16 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
app:dividerColor="#20222B" />
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:id="@+id/ivPointImage"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:src="@drawable/ic_add_image"/>
|
||||
</FrameLayout>
|
||||
</layout>
|
||||
@@ -24,10 +24,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:overScrollMode="never"
|
||||
android:background="@drawable/bg_stroke_gray"
|
||||
tools:itemCount="4"
|
||||
app:spanCount="4"
|
||||
android:paddingVertical="5dp"
|
||||
android:orientation="vertical"
|
||||
tools:listitem="@layout/list_item_point_image"
|
||||
tools:listitem="@layout/list_item_common_image"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:background="@color/black_bg">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvItemName"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:textColor="#6A6F87"
|
||||
android:textSize="16sp"
|
||||
tools:text="功能名称" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInputValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:background="@color/transparent"
|
||||
android:gravity="center_vertical"
|
||||
android:hint="请输入"
|
||||
android:textColor="@color/white"
|
||||
android:textColorHint="#757B8C"
|
||||
android:textCursorDrawable="@drawable/cursor_white"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvItemValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:background="@color/transparent"
|
||||
android:gravity="center_vertical"
|
||||
android:hint="请选择"
|
||||
android:drawablePadding="20dp"
|
||||
android:textColor="@color/white"
|
||||
android:textColorHint="#757B8C"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone"
|
||||
app:drawableEndCompat="@drawable/ic_arrow_down_gray" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvReportImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="3"
|
||||
tools:itemCount="6"
|
||||
tools:listitem="@layout/list_item_common_image" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
app:dividerColor="#282C38" />
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
@@ -38,5 +38,9 @@
|
||||
<!-- 遮罩层 -->
|
||||
<item name="android:backgroundDimAmount">0.5</item>
|
||||
</style>
|
||||
|
||||
<!-- <style name="Theme_dialog" parent="@android:style/Theme.Dialog">-->
|
||||
<!-- <item name="android:windowBackground">@android:color/transparent</item>-->
|
||||
<!-- <item name="android:windowNoTitle">true</item>-->
|
||||
<!-- <item name="android:windowIsFloating">true</item>-->
|
||||
<!-- </style>-->
|
||||
</resources>
|
||||
@@ -1,4 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="external_files" path="."/>
|
||||
<files-path name="my_files" path="." />
|
||||
<cache-path name="my_cache" path="." />
|
||||
<!-- <external-path name="my_external" path="." />-->
|
||||
<external-files-path name="external_files" path="." />
|
||||
<external-cache-path name="external_cache" path="." />
|
||||
<external-path name="external_storage" path="." />
|
||||
</paths>
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
apply plugin: 'com.android.library'
|
||||
//apply plugin: 'com.novoda.bintray-release'/
|
||||
//apply plugin: 'checkstyle'
|
||||
|
||||
android {
|
||||
// compileSdkVersion 29
|
||||
// buildToolsVersion '29.0.2'
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
// minSdkVersion 14
|
||||
// targetSdkVersion 29
|
||||
minSdk rootProject.ext.minSdkVersion
|
||||
targetSdk rootProject.ext.targetSdkVersion
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.annotation:annotation:1.1.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.0.0"
|
||||
implementation 'it.sephiroth.android.library.imagezoom:library:1.0.4'
|
||||
|
||||
compileOnly 'com.github.bumptech.glide:glide:4.9.0'
|
||||
compileOnly 'com.squareup.picasso:picasso:2.5.2'
|
||||
}
|
||||
|
||||
// jcenter configuration for novoda's bintray-release
|
||||
// $ ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=false
|
||||
//publish {
|
||||
// userOrg = 'zhihu'
|
||||
// groupId = 'com.zhihu.android'
|
||||
// artifactId = 'matisse'
|
||||
// publishVersion = '0.5.3-beta3'
|
||||
// desc = 'A well-designed local image selector for Android'
|
||||
// website = 'https://www.zhihu.com/'
|
||||
//}
|
||||
|
||||
//task javadoc(type: Javadoc) {
|
||||
// options.encoding = "utf-8"
|
||||
//}
|
||||
|
||||
//checkstyle {
|
||||
// toolVersion = '7.6.1'
|
||||
//}
|
||||
|
||||
//tasks.withType(Javadoc) {
|
||||
// options.addStringOption('Xdoclint:none', '-quiet')
|
||||
// options.addStringOption('encoding', 'UTF-8')
|
||||
//}
|
||||
|
||||
//task checkstyle(type:Checkstyle) {
|
||||
// description 'Runs Checkstyle inspection against matisse sourcesets.'
|
||||
// group = 'Code Quality'
|
||||
// configFile rootProject.file('checkstyle.xml')
|
||||
// ignoreFailures = false
|
||||
// showViolations true
|
||||
// classpath = files()
|
||||
// source 'src/main/java'
|
||||
//}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Library/android-sdk-macosx/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
-dontwarn com.squareup.okhttp.**
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2017 Zhihu Inc.
|
||||
|
||||
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.
|
||||
-->
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.zhihu.matisse">
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application>
|
||||
<activity android:name="com.zhihu.matisse.ui.MatisseActivity"/>
|
||||
<activity android:name="com.zhihu.matisse.internal.ui.AlbumPreviewActivity"/>
|
||||
<activity android:name="com.zhihu.matisse.internal.ui.SelectedPreviewActivity"/>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.zhihu.matisse.ui.MatisseActivity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Entry for Matisse's media selection.
|
||||
*/
|
||||
public final class Matisse {
|
||||
|
||||
private final WeakReference<Activity> mContext;
|
||||
private final WeakReference<Fragment> mFragment;
|
||||
|
||||
private Matisse(Activity activity) {
|
||||
this(activity, null);
|
||||
}
|
||||
|
||||
private Matisse(Fragment fragment) {
|
||||
this(fragment.getActivity(), fragment);
|
||||
}
|
||||
|
||||
private Matisse(Activity activity, Fragment fragment) {
|
||||
mContext = new WeakReference<>(activity);
|
||||
mFragment = new WeakReference<>(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Matisse from an Activity.
|
||||
* <p>
|
||||
* This Activity's {@link Activity#onActivityResult(int, int, Intent)} will be called when user
|
||||
* finishes selecting.
|
||||
*
|
||||
* @param activity Activity instance.
|
||||
* @return Matisse instance.
|
||||
*/
|
||||
public static Matisse from(Activity activity) {
|
||||
return new Matisse(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Matisse from a Fragment.
|
||||
* <p>
|
||||
* This Fragment's {@link Fragment#onActivityResult(int, int, Intent)} will be called when user
|
||||
* finishes selecting.
|
||||
*
|
||||
* @param fragment Fragment instance.
|
||||
* @return Matisse instance.
|
||||
*/
|
||||
public static Matisse from(Fragment fragment) {
|
||||
return new Matisse(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain user selected media' {@link Uri} list in the starting Activity or Fragment.
|
||||
*
|
||||
* @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
|
||||
* {@link Fragment#onActivityResult(int, int, Intent)}.
|
||||
* @return User selected media' {@link Uri} list.
|
||||
*/
|
||||
public static List<Uri> obtainResult(Intent data) {
|
||||
return data.getParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain user selected media path list in the starting Activity or Fragment.
|
||||
*
|
||||
* @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
|
||||
* {@link Fragment#onActivityResult(int, int, Intent)}.
|
||||
* @return User selected media path list.
|
||||
*/
|
||||
public static List<String> obtainPathResult(Intent data) {
|
||||
return data.getStringArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain state whether user decide to use selected media in original
|
||||
*
|
||||
* @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
|
||||
* {@link Fragment#onActivityResult(int, int, Intent)}.
|
||||
* @return Whether use original photo
|
||||
*/
|
||||
public static boolean obtainOriginalState(Intent data) {
|
||||
return data.getBooleanExtra(MatisseActivity.EXTRA_RESULT_ORIGINAL_ENABLE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME types the selection constrains on.
|
||||
* <p>
|
||||
* Types not included in the set will still be shown in the grid but can't be chosen.
|
||||
*
|
||||
* @param mimeTypes MIME types set user can choose from.
|
||||
* @return {@link SelectionCreator} to build select specifications.
|
||||
* @see MimeType
|
||||
* @see SelectionCreator
|
||||
*/
|
||||
public SelectionCreator choose(Set<MimeType> mimeTypes) {
|
||||
return this.choose(mimeTypes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME types the selection constrains on.
|
||||
* <p>
|
||||
* Types not included in the set will still be shown in the grid but can't be chosen.
|
||||
*
|
||||
* @param mimeTypes MIME types set user can choose from.
|
||||
* @param mediaTypeExclusive Whether can choose images and videos at the same time during one single choosing
|
||||
* process. true corresponds to not being able to choose images and videos at the same
|
||||
* time, and false corresponds to being able to do this.
|
||||
* @return {@link SelectionCreator} to build select specifications.
|
||||
* @see MimeType
|
||||
* @see SelectionCreator
|
||||
*/
|
||||
public SelectionCreator choose(Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
|
||||
return new SelectionCreator(this, mimeTypes, mediaTypeExclusive);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Activity getActivity() {
|
||||
return mContext.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Fragment getFragment() {
|
||||
return mFragment != null ? mFragment.get() : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* MIME Type enumeration to restrict selectable media on the selection activity. Matisse only supports images and
|
||||
* videos.
|
||||
* <p>
|
||||
* Good example of mime types Android supports:
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/MediaFile.java
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public enum MimeType {
|
||||
|
||||
// ============== images ==============
|
||||
JPEG("image/jpeg", arraySetOf(
|
||||
"jpg",
|
||||
"jpeg"
|
||||
)),
|
||||
PNG("image/png", arraySetOf(
|
||||
"png"
|
||||
)),
|
||||
GIF("image/gif", arraySetOf(
|
||||
"gif"
|
||||
)),
|
||||
BMP("image/x-ms-bmp", arraySetOf(
|
||||
"bmp"
|
||||
)),
|
||||
WEBP("image/webp", arraySetOf(
|
||||
"webp"
|
||||
)),
|
||||
|
||||
// ============== videos ==============
|
||||
MPEG("video/mpeg", arraySetOf(
|
||||
"mpeg",
|
||||
"mpg"
|
||||
)),
|
||||
MP4("video/mp4", arraySetOf(
|
||||
"mp4",
|
||||
"m4v"
|
||||
)),
|
||||
QUICKTIME("video/quicktime", arraySetOf(
|
||||
"mov"
|
||||
)),
|
||||
THREEGPP("video/3gpp", arraySetOf(
|
||||
"3gp",
|
||||
"3gpp"
|
||||
)),
|
||||
THREEGPP2("video/3gpp2", arraySetOf(
|
||||
"3g2",
|
||||
"3gpp2"
|
||||
)),
|
||||
MKV("video/x-matroska", arraySetOf(
|
||||
"mkv"
|
||||
)),
|
||||
WEBM("video/webm", arraySetOf(
|
||||
"webm"
|
||||
)),
|
||||
TS("video/mp2ts", arraySetOf(
|
||||
"ts"
|
||||
)),
|
||||
AVI("video/avi", arraySetOf(
|
||||
"avi"
|
||||
));
|
||||
|
||||
private final String mMimeTypeName;
|
||||
private final Set<String> mExtensions;
|
||||
|
||||
MimeType(String mimeTypeName, Set<String> extensions) {
|
||||
mMimeTypeName = mimeTypeName;
|
||||
mExtensions = extensions;
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofAll() {
|
||||
return EnumSet.allOf(MimeType.class);
|
||||
}
|
||||
|
||||
public static Set<MimeType> of(MimeType type, MimeType... rest) {
|
||||
return EnumSet.of(type, rest);
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofImage() {
|
||||
return EnumSet.of(JPEG, PNG, GIF, BMP, WEBP);
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofImage(boolean onlyGif) {
|
||||
return EnumSet.of(GIF);
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofGif() {
|
||||
return ofImage(true);
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofVideo() {
|
||||
return EnumSet.of(MPEG, MP4, QUICKTIME, THREEGPP, THREEGPP2, MKV, WEBM, TS, AVI);
|
||||
}
|
||||
|
||||
public static boolean isImage(String mimeType) {
|
||||
if (mimeType == null) return false;
|
||||
return mimeType.startsWith("image");
|
||||
}
|
||||
|
||||
public static boolean isVideo(String mimeType) {
|
||||
if (mimeType == null) return false;
|
||||
return mimeType.startsWith("video");
|
||||
}
|
||||
|
||||
public static boolean isGif(String mimeType) {
|
||||
if (mimeType == null) return false;
|
||||
return mimeType.equals(MimeType.GIF.toString());
|
||||
}
|
||||
|
||||
private static Set<String> arraySetOf(String... suffixes) {
|
||||
return new ArraySet<>(Arrays.asList(suffixes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mMimeTypeName;
|
||||
}
|
||||
|
||||
public boolean checkType(ContentResolver resolver, Uri uri) {
|
||||
MimeTypeMap map = MimeTypeMap.getSingleton();
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
String type = map.getExtensionFromMimeType(resolver.getType(uri));
|
||||
String path = null;
|
||||
// lazy load the path and prevent resolve for multiple times
|
||||
boolean pathParsed = false;
|
||||
for (String extension : mExtensions) {
|
||||
if (extension.equals(type)) {
|
||||
return true;
|
||||
}
|
||||
if (!pathParsed) {
|
||||
// we only resolve the path for one time
|
||||
path = PhotoMetadataUtils.getPath(resolver, uri);
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
path = path.toLowerCase(Locale.US);
|
||||
}
|
||||
pathParsed = true;
|
||||
}
|
||||
if (path != null && path.endsWith(extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.zhihu.matisse.engine.ImageEngine;
|
||||
import com.zhihu.matisse.filter.Filter;
|
||||
import com.zhihu.matisse.internal.entity.CaptureStrategy;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.listener.OnCheckedListener;
|
||||
import com.zhihu.matisse.listener.OnSelectedListener;
|
||||
import com.zhihu.matisse.ui.MatisseActivity;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
|
||||
|
||||
/**
|
||||
* Fluent API for building media select specification.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class SelectionCreator {
|
||||
private final Matisse mMatisse;
|
||||
private final SelectionSpec mSelectionSpec;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
@IntDef({
|
||||
SCREEN_ORIENTATION_UNSPECIFIED,
|
||||
SCREEN_ORIENTATION_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_PORTRAIT,
|
||||
SCREEN_ORIENTATION_USER,
|
||||
SCREEN_ORIENTATION_BEHIND,
|
||||
SCREEN_ORIENTATION_SENSOR,
|
||||
SCREEN_ORIENTATION_NOSENSOR,
|
||||
SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_SENSOR_PORTRAIT,
|
||||
SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_REVERSE_PORTRAIT,
|
||||
SCREEN_ORIENTATION_FULL_SENSOR,
|
||||
SCREEN_ORIENTATION_USER_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_USER_PORTRAIT,
|
||||
SCREEN_ORIENTATION_FULL_USER,
|
||||
SCREEN_ORIENTATION_LOCKED
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ScreenOrientation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new specification builder on the context.
|
||||
*
|
||||
* @param matisse a requester context wrapper.
|
||||
* @param mimeTypes MIME type set to select.
|
||||
*/
|
||||
SelectionCreator(Matisse matisse, @NonNull Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
|
||||
mMatisse = matisse;
|
||||
mSelectionSpec = SelectionSpec.getCleanInstance();
|
||||
mSelectionSpec.mimeTypeSet = mimeTypes;
|
||||
mSelectionSpec.mediaTypeExclusive = mediaTypeExclusive;
|
||||
mSelectionSpec.orientation = SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show only one media type if choosing medias are only images or videos.
|
||||
*
|
||||
* @param showSingleMediaType whether to show only one media type, either images or videos.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
* @see SelectionSpec#onlyShowImages()
|
||||
* @see SelectionSpec#onlyShowVideos()
|
||||
*/
|
||||
public SelectionCreator showSingleMediaType(boolean showSingleMediaType) {
|
||||
mSelectionSpec.showSingleMediaType = showSingleMediaType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme for media selecting Activity.
|
||||
* <p>
|
||||
* There are two built-in themes:
|
||||
* 1. com.zhihu.matisse.R.style.Matisse_Zhihu;
|
||||
* 2. com.zhihu.matisse.R.style.Matisse_Dracula
|
||||
* you can define a custom theme derived from the above ones or other themes.
|
||||
*
|
||||
* @param themeId theme resource id. Default value is com.zhihu.matisse.R.style.Matisse_Zhihu.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator theme(@StyleRes int themeId) {
|
||||
mSelectionSpec.themeId = themeId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a auto-increased number or a check mark when user select media.
|
||||
*
|
||||
* @param countable true for a auto-increased number from 1, false for a check mark. Default
|
||||
* value is false.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator countable(boolean countable) {
|
||||
mSelectionSpec.countable = countable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum selectable count.
|
||||
*
|
||||
* @param maxSelectable Maximum selectable count. Default value is 1.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator maxSelectable(int maxSelectable) {
|
||||
if (maxSelectable < 1)
|
||||
throw new IllegalArgumentException("maxSelectable must be greater than or equal to one");
|
||||
if (mSelectionSpec.maxImageSelectable > 0 || mSelectionSpec.maxVideoSelectable > 0)
|
||||
throw new IllegalStateException("already set maxImageSelectable and maxVideoSelectable");
|
||||
mSelectionSpec.maxSelectable = maxSelectable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only useful when {@link SelectionSpec#mediaTypeExclusive} set true and you want to set different maximum
|
||||
* selectable files for image and video media types.
|
||||
*
|
||||
* @param maxImageSelectable Maximum selectable count for image.
|
||||
* @param maxVideoSelectable Maximum selectable count for video.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator maxSelectablePerMediaType(int maxImageSelectable, int maxVideoSelectable) {
|
||||
if (maxImageSelectable < 1 || maxVideoSelectable < 1)
|
||||
throw new IllegalArgumentException(("max selectable must be greater than or equal to one"));
|
||||
mSelectionSpec.maxSelectable = -1;
|
||||
mSelectionSpec.maxImageSelectable = maxImageSelectable;
|
||||
mSelectionSpec.maxVideoSelectable = maxVideoSelectable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filter to filter each selecting item.
|
||||
*
|
||||
* @param filter {@link Filter}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator addFilter(@NonNull Filter filter) {
|
||||
if (mSelectionSpec.filters == null) {
|
||||
mSelectionSpec.filters = new ArrayList<>();
|
||||
}
|
||||
if (filter == null) throw new IllegalArgumentException("filter cannot be null");
|
||||
mSelectionSpec.filters.add(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the photo capturing is enabled or not on the media grid view.
|
||||
* <p>
|
||||
* If this value is set true, photo capturing entry will appear only on All Media's page.
|
||||
*
|
||||
* @param enable Whether to enable capturing or not. Default value is false;
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator capture(boolean enable) {
|
||||
mSelectionSpec.capture = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a original photo check options.Let users decide whether use original photo after select
|
||||
*
|
||||
* @param enable Whether to enable original photo or not
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator originalEnable(boolean enable) {
|
||||
mSelectionSpec.originalable = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines Whether to hide top and bottom toolbar in PreView mode ,when user tap the picture
|
||||
* @param enable
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator autoHideToolbarOnSingleTap(boolean enable) {
|
||||
mSelectionSpec.autoHideToobar = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum original size,the unit is MB. Only useful when {link@originalEnable} set true
|
||||
*
|
||||
* @param size Maximum original size. Default value is Integer.MAX_VALUE
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator maxOriginalSize(int size) {
|
||||
mSelectionSpec.originalMaxSize = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture strategy provided for the location to save photos including internal and external
|
||||
* storage and also a authority for {@link androidx.core.content.FileProvider}.
|
||||
*
|
||||
* @param captureStrategy {@link CaptureStrategy}, needed only when capturing is enabled.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator captureStrategy(CaptureStrategy captureStrategy) {
|
||||
mSelectionSpec.captureStrategy = captureStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the desired orientation of this activity.
|
||||
*
|
||||
* @param orientation An orientation constant as used in {@link ScreenOrientation}.
|
||||
* Default value is {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
* @see Activity#setRequestedOrientation(int)
|
||||
*/
|
||||
public SelectionCreator restrictOrientation(@ScreenOrientation int orientation) {
|
||||
mSelectionSpec.orientation = orientation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a fixed span count for the media grid. Same for different screen orientations.
|
||||
* <p>
|
||||
* This will be ignored when {@link #gridExpectedSize(int)} is set.
|
||||
*
|
||||
* @param spanCount Requested span count.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator spanCount(int spanCount) {
|
||||
if (spanCount < 1) throw new IllegalArgumentException("spanCount cannot be less than 1");
|
||||
mSelectionSpec.spanCount = spanCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set expected size for media grid to adapt to different screen sizes. This won't necessarily
|
||||
* be applied cause the media grid should fill the view container. The measured media grid's
|
||||
* size will be as close to this value as possible.
|
||||
*
|
||||
* @param size Expected media grid size in pixel.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator gridExpectedSize(int size) {
|
||||
mSelectionSpec.gridExpectedSize = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Photo thumbnail's scale compared to the View's size. It should be a float value in (0.0,
|
||||
* 1.0].
|
||||
*
|
||||
* @param scale Thumbnail's scale in (0.0, 1.0]. Default value is 0.5.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator thumbnailScale(float scale) {
|
||||
if (scale <= 0f || scale > 1f)
|
||||
throw new IllegalArgumentException("Thumbnail scale must be between (0.0, 1.0]");
|
||||
mSelectionSpec.thumbnailScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an image engine.
|
||||
* <p>
|
||||
* There are two built-in image engines:
|
||||
* 1. {@link com.zhihu.matisse.engine.impl.GlideEngine}
|
||||
* 2. {@link com.zhihu.matisse.engine.impl.PicassoEngine}
|
||||
* And you can implement your own image engine.
|
||||
*
|
||||
* @param imageEngine {@link ImageEngine}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator imageEngine(ImageEngine imageEngine) {
|
||||
mSelectionSpec.imageEngine = imageEngine;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener for callback immediately when user select or unselect something.
|
||||
* <p>
|
||||
* It's a redundant API with {@link Matisse#obtainResult(Intent)},
|
||||
* we only suggest you to use this API when you need to do something immediately.
|
||||
*
|
||||
* @param listener {@link OnSelectedListener}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
@NonNull
|
||||
public SelectionCreator setOnSelectedListener(@Nullable OnSelectedListener listener) {
|
||||
mSelectionSpec.onSelectedListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener for callback immediately when user check or uncheck original.
|
||||
*
|
||||
* @param listener {@link OnSelectedListener}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator setOnCheckedListener(@Nullable OnCheckedListener listener) {
|
||||
mSelectionSpec.onCheckedListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start to select media and wait for result.
|
||||
*
|
||||
* @param requestCode Identity of the request Activity or Fragment.
|
||||
*/
|
||||
public void forResult(int requestCode) {
|
||||
Activity activity = mMatisse.getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(activity, MatisseActivity.class);
|
||||
|
||||
Fragment fragment = mMatisse.getFragment();
|
||||
if (fragment != null) {
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
} else {
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
public SelectionCreator showPreview(boolean showPreview) {
|
||||
mSelectionSpec.showPreview = showPreview;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void forResultLaunch(Consumer<Intent> consumer) {
|
||||
Activity activity = mMatisse.getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(activity, MatisseActivity.class);
|
||||
if (consumer!=null) {
|
||||
consumer.accept(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.engine;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* Image loader interface. There are predefined {@link com.zhihu.matisse.engine.impl.GlideEngine}
|
||||
* and {@link com.zhihu.matisse.engine.impl.PicassoEngine}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public interface ImageEngine {
|
||||
|
||||
/**
|
||||
* Load thumbnail of a static image resource.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resize Desired size of the origin image
|
||||
* @param placeholder Placeholder drawable when image is not loaded yet
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Load thumbnail of a gif image resource. You don't have to load an animated gif when it's only
|
||||
* a thumbnail tile.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resize Desired size of the origin image
|
||||
* @param placeholder Placeholder drawable when image is not loaded yet
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Load a static image resource.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resizeX Desired x-size of the origin image
|
||||
* @param resizeY Desired y-size of the origin image
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Load a gif image resource.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resizeX Desired x-size of the origin image
|
||||
* @param resizeY Desired y-size of the origin image
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Whether this implementation supports animated gif.
|
||||
* Just knowledge of it, convenient for users.
|
||||
*
|
||||
* @return true support animated gif, false do not support animated gif.
|
||||
*/
|
||||
boolean supportAnimatedGif();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.engine.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.zhihu.matisse.engine.ImageEngine;
|
||||
|
||||
/**
|
||||
* {@link ImageEngine} implementation using Glide.
|
||||
*/
|
||||
|
||||
public class GlideEngine implements ImageEngine {
|
||||
|
||||
@Override
|
||||
public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
|
||||
Glide.with(context)
|
||||
.asBitmap() // some .jpeg files are actually gif
|
||||
.load(uri)
|
||||
.apply(new RequestOptions()
|
||||
.override(resize, resize)
|
||||
.placeholder(placeholder)
|
||||
.centerCrop())
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView,
|
||||
Uri uri) {
|
||||
Glide.with(context)
|
||||
.asBitmap() // some .jpeg files are actually gif
|
||||
.load(uri)
|
||||
.apply(new RequestOptions()
|
||||
.override(resize, resize)
|
||||
.placeholder(placeholder)
|
||||
.centerCrop())
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
|
||||
Glide.with(context)
|
||||
.load(uri)
|
||||
.apply(new RequestOptions()
|
||||
.override(resizeX, resizeY)
|
||||
.priority(Priority.HIGH)
|
||||
.fitCenter())
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
|
||||
Glide.with(context)
|
||||
.asGif()
|
||||
.load(uri)
|
||||
.apply(new RequestOptions()
|
||||
.override(resizeX, resizeY)
|
||||
.priority(Priority.HIGH)
|
||||
.fitCenter())
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportAnimatedGif() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.engine.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.zhihu.matisse.engine.ImageEngine;
|
||||
|
||||
/**
|
||||
* {@link ImageEngine} implementation using Picasso.
|
||||
*/
|
||||
|
||||
public class PicassoEngine implements ImageEngine {
|
||||
|
||||
@Override
|
||||
public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
|
||||
Picasso.with(context).load(uri).placeholder(placeholder)
|
||||
.resize(resize, resize)
|
||||
.centerCrop()
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView,
|
||||
Uri uri) {
|
||||
loadThumbnail(context, resize, placeholder, imageView, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
|
||||
Picasso.with(context).load(uri).resize(resizeX, resizeY).priority(Picasso.Priority.HIGH)
|
||||
.centerInside().into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
|
||||
loadImage(context, resizeX, resizeY, imageView, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportAnimatedGif() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.filter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.zhihu.matisse.MimeType;
|
||||
import com.zhihu.matisse.SelectionCreator;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.IncapableCause;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Filter for choosing a {@link Item}. You can add multiple Filters through
|
||||
* {@link SelectionCreator#addFilter(Filter)}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Filter {
|
||||
/**
|
||||
* Convenient constant for a minimum value.
|
||||
*/
|
||||
public static final int MIN = 0;
|
||||
/**
|
||||
* Convenient constant for a maximum value.
|
||||
*/
|
||||
public static final int MAX = Integer.MAX_VALUE;
|
||||
/**
|
||||
* Convenient constant for 1024.
|
||||
*/
|
||||
public static final int K = 1024;
|
||||
|
||||
/**
|
||||
* Against what mime types this filter applies.
|
||||
*/
|
||||
protected abstract Set<MimeType> constraintTypes();
|
||||
|
||||
/**
|
||||
* Invoked for filtering each item.
|
||||
*
|
||||
* @return null if selectable, {@link IncapableCause} if not selectable.
|
||||
*/
|
||||
public abstract IncapableCause filter(Context context, Item item);
|
||||
|
||||
/**
|
||||
* Whether an {@link Item} need filtering.
|
||||
*/
|
||||
protected boolean needFiltering(Context context, Item item) {
|
||||
for (MimeType type : constraintTypes()) {
|
||||
if (type.checkType(context.getContentResolver(), item.getContentUri())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.entity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.loader.AlbumLoader;
|
||||
|
||||
public class Album implements Parcelable {
|
||||
public static final Creator<Album> CREATOR = new Creator<Album>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Album createFromParcel(Parcel source) {
|
||||
return new Album(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Album[] newArray(int size) {
|
||||
return new Album[size];
|
||||
}
|
||||
};
|
||||
public static final String ALBUM_ID_ALL = String.valueOf(-1);
|
||||
public static final String ALBUM_NAME_ALL = "All";
|
||||
|
||||
private final String mId;
|
||||
private final Uri mCoverUri;
|
||||
private final String mDisplayName;
|
||||
private long mCount;
|
||||
|
||||
public Album(String id, Uri coverUri, String albumName, long count) {
|
||||
mId = id;
|
||||
mCoverUri = coverUri;
|
||||
mDisplayName = albumName;
|
||||
mCount = count;
|
||||
}
|
||||
|
||||
private Album(Parcel source) {
|
||||
mId = source.readString();
|
||||
mCoverUri = source.readParcelable(Uri.class.getClassLoader());
|
||||
mDisplayName = source.readString();
|
||||
mCount = source.readLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Album} entity from the {@link Cursor}.
|
||||
* This method is not responsible for managing cursor resource, such as close, iterate, and so on.
|
||||
*/
|
||||
public static Album valueOf(Cursor cursor) {
|
||||
String clumn = cursor.getString(cursor.getColumnIndex(AlbumLoader.COLUMN_URI));
|
||||
return new Album(
|
||||
cursor.getString(cursor.getColumnIndex("bucket_id")),
|
||||
Uri.parse(clumn != null ? clumn : ""),
|
||||
cursor.getString(cursor.getColumnIndex("bucket_display_name")),
|
||||
cursor.getLong(cursor.getColumnIndex(AlbumLoader.COLUMN_COUNT)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeParcelable(mCoverUri, 0);
|
||||
dest.writeString(mDisplayName);
|
||||
dest.writeLong(mCount);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public Uri getCoverUri() {
|
||||
return mCoverUri;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
public void addCaptureCount() {
|
||||
mCount++;
|
||||
}
|
||||
|
||||
public String getDisplayName(Context context) {
|
||||
if (isAll()) {
|
||||
return context.getString(R.string.album_name_all);
|
||||
}
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
public boolean isAll() {
|
||||
return ALBUM_ID_ALL.equals(mId);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mCount == 0;
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.entity;
|
||||
|
||||
public class CaptureStrategy {
|
||||
|
||||
public final boolean isPublic;
|
||||
public final String authority;
|
||||
public final String directory;
|
||||
|
||||
public CaptureStrategy(boolean isPublic, String authority) {
|
||||
this(isPublic, authority, null);
|
||||
}
|
||||
|
||||
public CaptureStrategy(boolean isPublic, String authority, String directory) {
|
||||
this.isPublic = isPublic;
|
||||
this.authority = authority;
|
||||
this.directory = directory;
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.entity;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.zhihu.matisse.internal.ui.widget.IncapableDialog;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class IncapableCause {
|
||||
public static final int TOAST = 0x00;
|
||||
public static final int DIALOG = 0x01;
|
||||
public static final int NONE = 0x02;
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({TOAST, DIALOG, NONE})
|
||||
public @interface Form {
|
||||
}
|
||||
|
||||
private int mForm = TOAST;
|
||||
private String mTitle;
|
||||
private String mMessage;
|
||||
|
||||
public IncapableCause(String message) {
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public IncapableCause(String title, String message) {
|
||||
mTitle = title;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public IncapableCause(@Form int form, String message) {
|
||||
mForm = form;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public IncapableCause(@Form int form, String title, String message) {
|
||||
mForm = form;
|
||||
mTitle = title;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public static void handleCause(Context context, IncapableCause cause) {
|
||||
if (cause == null)
|
||||
return;
|
||||
|
||||
switch (cause.mForm) {
|
||||
case NONE:
|
||||
// do nothing.
|
||||
break;
|
||||
case DIALOG:
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance(cause.mTitle, cause.mMessage);
|
||||
incapableDialog.show(((FragmentActivity) context).getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
break;
|
||||
case TOAST:
|
||||
default:
|
||||
Toast.makeText(context, cause.mMessage, Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.entity;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.zhihu.matisse.MimeType;
|
||||
|
||||
public class Item implements Parcelable {
|
||||
public static final Creator<Item> CREATOR = new Creator<Item>() {
|
||||
@Override
|
||||
@Nullable
|
||||
public Item createFromParcel(Parcel source) {
|
||||
return new Item(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item[] newArray(int size) {
|
||||
return new Item[size];
|
||||
}
|
||||
};
|
||||
public static final long ITEM_ID_CAPTURE = -1;
|
||||
public static final String ITEM_DISPLAY_NAME_CAPTURE = "Capture";
|
||||
public final long id;
|
||||
public final String mimeType;
|
||||
public final Uri uri;
|
||||
public final long size;
|
||||
public final long duration; // only for video, in ms
|
||||
|
||||
private Item(long id, String mimeType, long size, long duration) {
|
||||
this.id = id;
|
||||
this.mimeType = mimeType;
|
||||
Uri contentUri;
|
||||
if (isImage()) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if (isVideo()) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else {
|
||||
// ?
|
||||
contentUri = MediaStore.Files.getContentUri("external");
|
||||
}
|
||||
this.uri = ContentUris.withAppendedId(contentUri, id);
|
||||
this.size = size;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
private Item(Parcel source) {
|
||||
id = source.readLong();
|
||||
mimeType = source.readString();
|
||||
uri = source.readParcelable(Uri.class.getClassLoader());
|
||||
size = source.readLong();
|
||||
duration = source.readLong();
|
||||
}
|
||||
|
||||
public static Item valueOf(Cursor cursor) {
|
||||
return new Item(cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)),
|
||||
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)),
|
||||
cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)),
|
||||
cursor.getLong(cursor.getColumnIndex("duration")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(id);
|
||||
dest.writeString(mimeType);
|
||||
dest.writeParcelable(uri, 0);
|
||||
dest.writeLong(size);
|
||||
dest.writeLong(duration);
|
||||
}
|
||||
|
||||
public Uri getContentUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public boolean isCapture() {
|
||||
return id == ITEM_ID_CAPTURE;
|
||||
}
|
||||
|
||||
public boolean isImage() {
|
||||
return MimeType.isImage(mimeType);
|
||||
}
|
||||
|
||||
public boolean isGif() {
|
||||
return MimeType.isGif(mimeType);
|
||||
}
|
||||
|
||||
public boolean isVideo() {
|
||||
return MimeType.isVideo(mimeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Item other = (Item) obj;
|
||||
return id == other.id
|
||||
&& (mimeType != null && mimeType.equals(other.mimeType)
|
||||
|| (mimeType == null && other.mimeType == null))
|
||||
&& (uri != null && uri.equals(other.uri)
|
||||
|| (uri == null && other.uri == null))
|
||||
&& size == other.size
|
||||
&& duration == other.duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 1;
|
||||
result = 31 * result + Long.valueOf(id).hashCode();
|
||||
if (mimeType != null) {
|
||||
result = 31 * result + mimeType.hashCode();
|
||||
}
|
||||
result = 31 * result + uri.hashCode();
|
||||
result = 31 * result + Long.valueOf(size).hashCode();
|
||||
result = 31 * result + Long.valueOf(duration).hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.entity;
|
||||
|
||||
import android.content.pm.ActivityInfo;
|
||||
|
||||
import androidx.annotation.StyleRes;
|
||||
|
||||
import com.zhihu.matisse.MimeType;
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.engine.ImageEngine;
|
||||
import com.zhihu.matisse.engine.impl.GlideEngine;
|
||||
import com.zhihu.matisse.filter.Filter;
|
||||
import com.zhihu.matisse.listener.OnCheckedListener;
|
||||
import com.zhihu.matisse.listener.OnSelectedListener;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class SelectionSpec {
|
||||
|
||||
public Set<MimeType> mimeTypeSet;
|
||||
public boolean mediaTypeExclusive;
|
||||
public boolean showSingleMediaType;
|
||||
@StyleRes
|
||||
public int themeId;
|
||||
public int orientation;
|
||||
public boolean countable;
|
||||
public int maxSelectable;
|
||||
public int maxImageSelectable;
|
||||
public int maxVideoSelectable;
|
||||
public List<Filter> filters;
|
||||
public boolean capture;
|
||||
public CaptureStrategy captureStrategy;
|
||||
public int spanCount;
|
||||
public int gridExpectedSize;
|
||||
public float thumbnailScale;
|
||||
public ImageEngine imageEngine;
|
||||
public boolean hasInited;
|
||||
public OnSelectedListener onSelectedListener;
|
||||
public boolean originalable;
|
||||
public boolean autoHideToobar;
|
||||
public int originalMaxSize;
|
||||
public OnCheckedListener onCheckedListener;
|
||||
public boolean showPreview;
|
||||
|
||||
private SelectionSpec() {
|
||||
}
|
||||
|
||||
public static SelectionSpec getInstance() {
|
||||
return InstanceHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public static SelectionSpec getCleanInstance() {
|
||||
SelectionSpec selectionSpec = getInstance();
|
||||
selectionSpec.reset();
|
||||
return selectionSpec;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
mimeTypeSet = null;
|
||||
mediaTypeExclusive = true;
|
||||
showSingleMediaType = false;
|
||||
themeId = R.style.Matisse_Zhihu;
|
||||
orientation = 0;
|
||||
countable = false;
|
||||
maxSelectable = 1;
|
||||
maxImageSelectable = 0;
|
||||
maxVideoSelectable = 0;
|
||||
filters = null;
|
||||
capture = false;
|
||||
captureStrategy = null;
|
||||
spanCount = 3;
|
||||
gridExpectedSize = 0;
|
||||
thumbnailScale = 0.5f;
|
||||
imageEngine = new GlideEngine();
|
||||
hasInited = true;
|
||||
originalable = false;
|
||||
autoHideToobar = false;
|
||||
originalMaxSize = Integer.MAX_VALUE;
|
||||
showPreview = true;
|
||||
}
|
||||
|
||||
public boolean singleSelectionModeEnabled() {
|
||||
return !countable && (maxSelectable == 1 || (maxImageSelectable == 1 && maxVideoSelectable == 1));
|
||||
}
|
||||
|
||||
public boolean needOrientationRestriction() {
|
||||
return orientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
}
|
||||
|
||||
public boolean onlyShowImages() {
|
||||
return showSingleMediaType && MimeType.ofImage().containsAll(mimeTypeSet);
|
||||
}
|
||||
|
||||
public boolean onlyShowVideos() {
|
||||
return showSingleMediaType && MimeType.ofVideo().containsAll(mimeTypeSet);
|
||||
}
|
||||
|
||||
public boolean onlyShowGif() {
|
||||
return showSingleMediaType && MimeType.ofGif().equals(mimeTypeSet);
|
||||
}
|
||||
|
||||
private static final class InstanceHolder {
|
||||
private static final SelectionSpec INSTANCE = new SelectionSpec();
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.loader;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
import com.zhihu.matisse.MimeType;
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Load all albums (grouped by bucket_id) into a single cursor.
|
||||
*/
|
||||
public class AlbumLoader extends CursorLoader {
|
||||
|
||||
private static final String COLUMN_BUCKET_ID = "bucket_id";
|
||||
private static final String COLUMN_BUCKET_DISPLAY_NAME = "bucket_display_name";
|
||||
public static final String COLUMN_URI = "uri";
|
||||
public static final String COLUMN_COUNT = "count";
|
||||
private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
|
||||
|
||||
private static final String[] COLUMNS = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
COLUMN_BUCKET_ID,
|
||||
COLUMN_BUCKET_DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
COLUMN_URI,
|
||||
COLUMN_COUNT};
|
||||
|
||||
private static final String[] PROJECTION = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
COLUMN_BUCKET_ID,
|
||||
COLUMN_BUCKET_DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
"COUNT(*) AS " + COLUMN_COUNT};
|
||||
|
||||
private static final String[] PROJECTION_29 = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
COLUMN_BUCKET_ID,
|
||||
COLUMN_BUCKET_DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE};
|
||||
|
||||
// === params for showSingleMediaType: false ===
|
||||
private static final String SELECTION =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ ") GROUP BY (bucket_id";
|
||||
private static final String SELECTION_29 =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
private static final String[] SELECTION_ARGS = {
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
|
||||
};
|
||||
// =============================================
|
||||
|
||||
// === params for showSingleMediaType: true ===
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ ") GROUP BY (bucket_id";
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE_29 =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
|
||||
return new String[]{String.valueOf(mediaType)};
|
||||
}
|
||||
// =============================================
|
||||
|
||||
// === params for showSingleMediaType: true ===
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?"
|
||||
+ ") GROUP BY (bucket_id";
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29 =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?";
|
||||
|
||||
private static String[] getSelectionArgsForSingleMediaGifType(int mediaType) {
|
||||
return new String[]{String.valueOf(mediaType), "image/gif"};
|
||||
}
|
||||
// =============================================
|
||||
|
||||
private static final String BUCKET_ORDER_BY = "datetaken DESC";
|
||||
|
||||
private AlbumLoader(Context context, String selection, String[] selectionArgs) {
|
||||
super(
|
||||
context,
|
||||
QUERY_URI,
|
||||
beforeAndroidTen() ? PROJECTION : PROJECTION_29,
|
||||
selection,
|
||||
selectionArgs,
|
||||
BUCKET_ORDER_BY
|
||||
);
|
||||
}
|
||||
|
||||
public static CursorLoader newInstance(Context context) {
|
||||
String selection;
|
||||
String[] selectionArgs;
|
||||
if (SelectionSpec.getInstance().onlyShowGif()) {
|
||||
selection = beforeAndroidTen()
|
||||
? SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE : SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29;
|
||||
selectionArgs = getSelectionArgsForSingleMediaGifType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
} else if (SelectionSpec.getInstance().onlyShowImages()) {
|
||||
selection = beforeAndroidTen()
|
||||
? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
|
||||
selectionArgs = getSelectionArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
} else if (SelectionSpec.getInstance().onlyShowVideos()) {
|
||||
selection = beforeAndroidTen()
|
||||
? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
|
||||
selectionArgs = getSelectionArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
selection = beforeAndroidTen() ? SELECTION : SELECTION_29;
|
||||
selectionArgs = SELECTION_ARGS;
|
||||
}
|
||||
return new AlbumLoader(context, selection, selectionArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
Cursor albums = super.loadInBackground();
|
||||
MatrixCursor allAlbum = new MatrixCursor(COLUMNS);
|
||||
|
||||
if (beforeAndroidTen()) {
|
||||
int totalCount = 0;
|
||||
Uri allAlbumCoverUri = null;
|
||||
MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
|
||||
if (albums != null) {
|
||||
while (albums.moveToNext()) {
|
||||
long fileId = albums.getLong(
|
||||
albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
|
||||
long bucketId = albums.getLong(
|
||||
albums.getColumnIndex(COLUMN_BUCKET_ID));
|
||||
String bucketDisplayName = albums.getString(
|
||||
albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
|
||||
String mimeType = albums.getString(
|
||||
albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
|
||||
Uri uri = getUri(albums);
|
||||
int count = albums.getInt(albums.getColumnIndex(COLUMN_COUNT));
|
||||
|
||||
otherAlbums.addRow(new String[]{
|
||||
Long.toString(fileId),
|
||||
Long.toString(bucketId), bucketDisplayName, mimeType, uri.toString(),
|
||||
String.valueOf(count)});
|
||||
totalCount += count;
|
||||
}
|
||||
if (albums.moveToFirst()) {
|
||||
allAlbumCoverUri = getUri(albums);
|
||||
}
|
||||
}
|
||||
|
||||
allAlbum.addRow(new String[]{
|
||||
Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
|
||||
allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
|
||||
String.valueOf(totalCount)});
|
||||
|
||||
return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
|
||||
} else {
|
||||
int totalCount = 0;
|
||||
Uri allAlbumCoverUri = null;
|
||||
|
||||
// Pseudo GROUP BY
|
||||
Map<Long, Long> countMap = new HashMap<>();
|
||||
if (albums != null) {
|
||||
while (albums.moveToNext()) {
|
||||
long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));
|
||||
|
||||
Long count = countMap.get(bucketId);
|
||||
if (count == null) {
|
||||
count = 1L;
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
countMap.put(bucketId, count);
|
||||
}
|
||||
}
|
||||
|
||||
MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
|
||||
if (albums != null) {
|
||||
if (albums.moveToFirst()) {
|
||||
allAlbumCoverUri = getUri(albums);
|
||||
|
||||
Set<Long> done = new HashSet<>();
|
||||
|
||||
do {
|
||||
long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));
|
||||
|
||||
if (done.contains(bucketId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long fileId = albums.getLong(
|
||||
albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
|
||||
String bucketDisplayName = albums.getString(
|
||||
albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
|
||||
String mimeType = albums.getString(
|
||||
albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
|
||||
Uri uri = getUri(albums);
|
||||
long count = countMap.get(bucketId);
|
||||
|
||||
otherAlbums.addRow(new String[]{
|
||||
Long.toString(fileId),
|
||||
Long.toString(bucketId),
|
||||
bucketDisplayName,
|
||||
mimeType,
|
||||
uri.toString(),
|
||||
String.valueOf(count)});
|
||||
done.add(bucketId);
|
||||
|
||||
totalCount += count;
|
||||
} while (albums.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
allAlbum.addRow(new String[]{
|
||||
Album.ALBUM_ID_ALL,
|
||||
Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
|
||||
allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
|
||||
String.valueOf(totalCount)});
|
||||
|
||||
return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
|
||||
}
|
||||
}
|
||||
|
||||
private static Uri getUri(Cursor cursor) {
|
||||
long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
|
||||
String mimeType = cursor.getString(
|
||||
cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
|
||||
Uri contentUri;
|
||||
|
||||
if (MimeType.isImage(mimeType)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if (MimeType.isVideo(mimeType)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else {
|
||||
// ?
|
||||
contentUri = MediaStore.Files.getContentUri("external");
|
||||
}
|
||||
|
||||
Uri uri = ContentUris.withAppendedId(contentUri, id);
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
// FIXME a dirty way to fix loading multiple times
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否是 Android 10 (Q) 之前的版本
|
||||
*/
|
||||
private static boolean beforeAndroidTen() {
|
||||
return android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.utils.MediaStoreCompat;
|
||||
|
||||
/**
|
||||
* Load images and videos into a single cursor.
|
||||
*/
|
||||
public class AlbumMediaLoader extends CursorLoader {
|
||||
private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
|
||||
private static final String[] PROJECTION = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
MediaStore.MediaColumns.SIZE,
|
||||
"duration"};
|
||||
|
||||
// === params for album ALL && showSingleMediaType: false ===
|
||||
private static final String SELECTION_ALL =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
private static final String[] SELECTION_ALL_ARGS = {
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
|
||||
};
|
||||
// ===========================================================
|
||||
|
||||
// === params for album ALL && showSingleMediaType: true ===
|
||||
private static final String SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
|
||||
return new String[]{String.valueOf(mediaType)};
|
||||
}
|
||||
// =========================================================
|
||||
|
||||
// === params for ordinary album && showSingleMediaType: false ===
|
||||
private static final String SELECTION_ALBUM =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND "
|
||||
+ " bucket_id=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionAlbumArgs(String albumId) {
|
||||
return new String[]{
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
|
||||
albumId
|
||||
};
|
||||
}
|
||||
// ===============================================================
|
||||
|
||||
// === params for ordinary album && showSingleMediaType: true ===
|
||||
private static final String SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND "
|
||||
+ " bucket_id=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionAlbumArgsForSingleMediaType(int mediaType, String albumId) {
|
||||
return new String[]{String.valueOf(mediaType), albumId};
|
||||
}
|
||||
// ===============================================================
|
||||
|
||||
// === params for album ALL && showSingleMediaType: true && MineType=="image/gif"
|
||||
private static final String SELECTION_ALL_FOR_GIF =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND "
|
||||
+ MediaStore.MediaColumns.MIME_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionArgsForGifType(int mediaType) {
|
||||
return new String[]{String.valueOf(mediaType), "image/gif"};
|
||||
}
|
||||
// ===============================================================
|
||||
|
||||
// === params for ordinary album && showSingleMediaType: true && MineType=="image/gif" ===
|
||||
private static final String SELECTION_ALBUM_FOR_GIF =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND "
|
||||
+ " bucket_id=?"
|
||||
+ " AND "
|
||||
+ MediaStore.MediaColumns.MIME_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionAlbumArgsForGifType(int mediaType, String albumId) {
|
||||
return new String[]{String.valueOf(mediaType), albumId, "image/gif"};
|
||||
}
|
||||
// ===============================================================
|
||||
|
||||
private static final String ORDER_BY = MediaStore.Images.Media.DATE_TAKEN + " DESC";
|
||||
private final boolean mEnableCapture;
|
||||
|
||||
private AlbumMediaLoader(Context context, String selection, String[] selectionArgs, boolean capture) {
|
||||
super(context, QUERY_URI, PROJECTION, selection, selectionArgs, ORDER_BY);
|
||||
mEnableCapture = capture;
|
||||
}
|
||||
|
||||
public static CursorLoader newInstance(Context context, Album album, boolean capture) {
|
||||
String selection;
|
||||
String[] selectionArgs;
|
||||
boolean enableCapture;
|
||||
|
||||
if (album.isAll()) {
|
||||
if (SelectionSpec.getInstance().onlyShowGif()) {
|
||||
selection = SELECTION_ALL_FOR_GIF;
|
||||
selectionArgs = getSelectionArgsForGifType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
} else if (SelectionSpec.getInstance().onlyShowImages()) {
|
||||
selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs =
|
||||
getSelectionArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
} else if (SelectionSpec.getInstance().onlyShowVideos()) {
|
||||
selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs =
|
||||
getSelectionArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
selection = SELECTION_ALL;
|
||||
selectionArgs = SELECTION_ALL_ARGS;
|
||||
}
|
||||
enableCapture = capture;
|
||||
} else {
|
||||
if (SelectionSpec.getInstance().onlyShowGif()) {
|
||||
selection = SELECTION_ALBUM_FOR_GIF;
|
||||
selectionArgs =
|
||||
getSelectionAlbumArgsForGifType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE, album.getId());
|
||||
} else if (SelectionSpec.getInstance().onlyShowImages()) {
|
||||
selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs =
|
||||
getSelectionAlbumArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE,
|
||||
album.getId());
|
||||
} else if (SelectionSpec.getInstance().onlyShowVideos()) {
|
||||
selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs = getSelectionAlbumArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO,
|
||||
album.getId());
|
||||
} else {
|
||||
selection = SELECTION_ALBUM;
|
||||
selectionArgs = getSelectionAlbumArgs(album.getId());
|
||||
}
|
||||
enableCapture = false;
|
||||
}
|
||||
return new AlbumMediaLoader(context, selection, selectionArgs, enableCapture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
Cursor result = super.loadInBackground();
|
||||
if (!mEnableCapture || !MediaStoreCompat.hasCameraFeature(getContext())) {
|
||||
return result;
|
||||
}
|
||||
MatrixCursor dummy = new MatrixCursor(PROJECTION);
|
||||
dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0});
|
||||
return new MergeCursor(new Cursor[]{dummy, result});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
// FIXME a dirty way to fix loading multiple times
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import com.zhihu.matisse.internal.loader.AlbumLoader;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class AlbumCollection implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private static final int LOADER_ID = 1;
|
||||
private static final String STATE_CURRENT_SELECTION = "state_current_selection";
|
||||
private WeakReference<Context> mContext;
|
||||
private LoaderManager mLoaderManager;
|
||||
private AlbumCallbacks mCallbacks;
|
||||
private int mCurrentSelection;
|
||||
private boolean mLoadFinished;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
mLoadFinished = false;
|
||||
return AlbumLoader.newInstance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mLoadFinished) {
|
||||
mLoadFinished = true;
|
||||
mCallbacks.onAlbumLoad(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.onAlbumReset();
|
||||
}
|
||||
|
||||
public void onCreate(FragmentActivity activity, AlbumCallbacks callbacks) {
|
||||
mContext = new WeakReference<Context>(activity);
|
||||
mLoaderManager = activity.getSupportLoaderManager();
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentSelection = savedInstanceState.getInt(STATE_CURRENT_SELECTION);
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putInt(STATE_CURRENT_SELECTION, mCurrentSelection);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(LOADER_ID);
|
||||
}
|
||||
mCallbacks = null;
|
||||
}
|
||||
|
||||
public void loadAlbums() {
|
||||
mLoaderManager.initLoader(LOADER_ID, null, this);
|
||||
}
|
||||
|
||||
public int getCurrentSelection() {
|
||||
return mCurrentSelection;
|
||||
}
|
||||
|
||||
public void setStateCurrentSelection(int currentSelection) {
|
||||
mCurrentSelection = currentSelection;
|
||||
}
|
||||
|
||||
public interface AlbumCallbacks {
|
||||
void onAlbumLoad(Cursor cursor);
|
||||
|
||||
void onAlbumReset();
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.loader.AlbumMediaLoader;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class AlbumMediaCollection implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private static final int LOADER_ID = 2;
|
||||
private static final String ARGS_ALBUM = "args_album";
|
||||
private static final String ARGS_ENABLE_CAPTURE = "args_enable_capture";
|
||||
private WeakReference<Context> mContext;
|
||||
private LoaderManager mLoaderManager;
|
||||
private AlbumMediaCallbacks mCallbacks;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Album album = args.getParcelable(ARGS_ALBUM);
|
||||
if (album == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AlbumMediaLoader.newInstance(context, album,
|
||||
album.isAll() && args.getBoolean(ARGS_ENABLE_CAPTURE, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.onAlbumMediaLoad(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.onAlbumMediaReset();
|
||||
}
|
||||
|
||||
public void onCreate(@NonNull FragmentActivity context, @NonNull AlbumMediaCallbacks callbacks) {
|
||||
mContext = new WeakReference<Context>(context);
|
||||
mLoaderManager = context.getSupportLoaderManager();
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(LOADER_ID);
|
||||
}
|
||||
mCallbacks = null;
|
||||
}
|
||||
|
||||
public void load(@Nullable Album target) {
|
||||
load(target, false);
|
||||
}
|
||||
|
||||
public void load(@Nullable Album target, boolean enableCapture) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARGS_ALBUM, target);
|
||||
args.putBoolean(ARGS_ENABLE_CAPTURE, enableCapture);
|
||||
mLoaderManager.initLoader(LOADER_ID, args, this);
|
||||
}
|
||||
|
||||
public interface AlbumMediaCallbacks {
|
||||
|
||||
void onAlbumMediaLoad(Cursor cursor);
|
||||
|
||||
void onAlbumMediaReset();
|
||||
}
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.IncapableCause;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.ui.widget.CheckView;
|
||||
import com.zhihu.matisse.internal.utils.PathUtils;
|
||||
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SelectedItemCollection {
|
||||
|
||||
public static final String STATE_SELECTION = "state_selection";
|
||||
public static final String STATE_COLLECTION_TYPE = "state_collection_type";
|
||||
/**
|
||||
* Empty collection
|
||||
*/
|
||||
public static final int COLLECTION_UNDEFINED = 0x00;
|
||||
/**
|
||||
* Collection only with images
|
||||
*/
|
||||
public static final int COLLECTION_IMAGE = 0x01;
|
||||
/**
|
||||
* Collection only with videos
|
||||
*/
|
||||
public static final int COLLECTION_VIDEO = 0x01 << 1;
|
||||
/**
|
||||
* Collection with images and videos.
|
||||
*/
|
||||
public static final int COLLECTION_MIXED = COLLECTION_IMAGE | COLLECTION_VIDEO;
|
||||
private final Context mContext;
|
||||
private Set<Item> mItems;
|
||||
private int mCollectionType = COLLECTION_UNDEFINED;
|
||||
|
||||
public SelectedItemCollection(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void onCreate(Bundle bundle) {
|
||||
if (bundle == null) {
|
||||
mItems = new LinkedHashSet<>();
|
||||
} else {
|
||||
List<Item> saved = bundle.getParcelableArrayList(STATE_SELECTION);
|
||||
mItems = new LinkedHashSet<>(saved);
|
||||
mCollectionType = bundle.getInt(STATE_COLLECTION_TYPE, COLLECTION_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultSelection(List<Item> uris) {
|
||||
mItems.addAll(uris);
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
|
||||
outState.putInt(STATE_COLLECTION_TYPE, mCollectionType);
|
||||
}
|
||||
|
||||
public Bundle getDataWithBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
|
||||
bundle.putInt(STATE_COLLECTION_TYPE, mCollectionType);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public boolean add(Item item) {
|
||||
if (typeConflict(item)) {
|
||||
throw new IllegalArgumentException("Can't select images and videos at the same time.");
|
||||
}
|
||||
boolean added = mItems.add(item);
|
||||
if (added) {
|
||||
if (mCollectionType == COLLECTION_UNDEFINED) {
|
||||
if (item.isImage()) {
|
||||
mCollectionType = COLLECTION_IMAGE;
|
||||
} else if (item.isVideo()) {
|
||||
mCollectionType = COLLECTION_VIDEO;
|
||||
}
|
||||
} else if (mCollectionType == COLLECTION_IMAGE) {
|
||||
if (item.isVideo()) {
|
||||
mCollectionType = COLLECTION_MIXED;
|
||||
}
|
||||
} else if (mCollectionType == COLLECTION_VIDEO) {
|
||||
if (item.isImage()) {
|
||||
mCollectionType = COLLECTION_MIXED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
public boolean remove(Item item) {
|
||||
boolean removed = mItems.remove(item);
|
||||
if (removed) {
|
||||
if (mItems.size() == 0) {
|
||||
mCollectionType = COLLECTION_UNDEFINED;
|
||||
} else {
|
||||
if (mCollectionType == COLLECTION_MIXED) {
|
||||
refineCollectionType();
|
||||
}
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void overwrite(ArrayList<Item> items, int collectionType) {
|
||||
if (items.size() == 0) {
|
||||
mCollectionType = COLLECTION_UNDEFINED;
|
||||
} else {
|
||||
mCollectionType = collectionType;
|
||||
}
|
||||
mItems.clear();
|
||||
mItems.addAll(items);
|
||||
}
|
||||
|
||||
|
||||
public List<Item> asList() {
|
||||
return new ArrayList<>(mItems);
|
||||
}
|
||||
|
||||
public List<Uri> asListOfUri() {
|
||||
List<Uri> uris = new ArrayList<>();
|
||||
for (Item item : mItems) {
|
||||
uris.add(item.getContentUri());
|
||||
}
|
||||
return uris;
|
||||
}
|
||||
|
||||
public List<String> asListOfString() {
|
||||
List<String> paths = new ArrayList<>();
|
||||
for (Item item : mItems) {
|
||||
paths.add(PathUtils.getPath(mContext, item.getContentUri()));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mItems == null || mItems.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isSelected(Item item) {
|
||||
return mItems.contains(item);
|
||||
}
|
||||
|
||||
public IncapableCause isAcceptable(Item item) {
|
||||
if (maxSelectableReached()) {
|
||||
int maxSelectable = currentMaxSelectable();
|
||||
String cause;
|
||||
|
||||
try {
|
||||
cause = mContext.getResources().getQuantityString(
|
||||
R.plurals.error_over_count,
|
||||
maxSelectable,
|
||||
maxSelectable
|
||||
);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
cause = mContext.getString(
|
||||
R.string.error_over_count,
|
||||
maxSelectable
|
||||
);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
cause = mContext.getString(
|
||||
R.string.error_over_count,
|
||||
maxSelectable
|
||||
);
|
||||
}
|
||||
|
||||
return new IncapableCause(cause);
|
||||
} else if (typeConflict(item)) {
|
||||
return new IncapableCause(mContext.getString(R.string.error_type_conflict));
|
||||
}
|
||||
|
||||
return PhotoMetadataUtils.isAcceptable(mContext, item);
|
||||
}
|
||||
|
||||
public boolean maxSelectableReached() {
|
||||
return mItems.size() == currentMaxSelectable();
|
||||
}
|
||||
|
||||
// depends
|
||||
private int currentMaxSelectable() {
|
||||
SelectionSpec spec = SelectionSpec.getInstance();
|
||||
if (spec.maxSelectable > 0) {
|
||||
return spec.maxSelectable;
|
||||
} else if (mCollectionType == COLLECTION_IMAGE) {
|
||||
return spec.maxImageSelectable;
|
||||
} else if (mCollectionType == COLLECTION_VIDEO) {
|
||||
return spec.maxVideoSelectable;
|
||||
} else {
|
||||
return spec.maxSelectable;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCollectionType() {
|
||||
return mCollectionType;
|
||||
}
|
||||
|
||||
private void refineCollectionType() {
|
||||
boolean hasImage = false;
|
||||
boolean hasVideo = false;
|
||||
for (Item i : mItems) {
|
||||
if (i.isImage() && !hasImage) hasImage = true;
|
||||
if (i.isVideo() && !hasVideo) hasVideo = true;
|
||||
}
|
||||
if (hasImage && hasVideo) {
|
||||
mCollectionType = COLLECTION_MIXED;
|
||||
} else if (hasImage) {
|
||||
mCollectionType = COLLECTION_IMAGE;
|
||||
} else if (hasVideo) {
|
||||
mCollectionType = COLLECTION_VIDEO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether there will be conflict media types. A user can only select images and videos at the same time
|
||||
* while {@link SelectionSpec#mediaTypeExclusive} is set to false.
|
||||
*/
|
||||
public boolean typeConflict(Item item) {
|
||||
return SelectionSpec.getInstance().mediaTypeExclusive
|
||||
&& ((item.isImage() && (mCollectionType == COLLECTION_VIDEO || mCollectionType == COLLECTION_MIXED))
|
||||
|| (item.isVideo() && (mCollectionType == COLLECTION_IMAGE || mCollectionType == COLLECTION_MIXED)));
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
public int checkedNumOf(Item item) {
|
||||
int index = new ArrayList<>(mItems).indexOf(item);
|
||||
return index == -1 ? CheckView.UNCHECKED : index + 1;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.model.AlbumMediaCollection;
|
||||
import com.zhihu.matisse.internal.ui.adapter.PreviewPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumPreviewActivity extends BasePreviewActivity implements
|
||||
AlbumMediaCollection.AlbumMediaCallbacks {
|
||||
|
||||
public static final String EXTRA_ALBUM = "extra_album";
|
||||
public static final String EXTRA_ITEM = "extra_item";
|
||||
|
||||
private AlbumMediaCollection mCollection = new AlbumMediaCollection();
|
||||
|
||||
private boolean mIsAlreadySetPosition;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!SelectionSpec.getInstance().hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
mCollection.onCreate(this, this);
|
||||
Album album = getIntent().getParcelableExtra(EXTRA_ALBUM);
|
||||
mCollection.load(album);
|
||||
|
||||
Item item = getIntent().getParcelableExtra(EXTRA_ITEM);
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item));
|
||||
} else {
|
||||
mCheckView.setChecked(mSelectedCollection.isSelected(item));
|
||||
}
|
||||
updateSize(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mCollection.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaLoad(Cursor cursor) {
|
||||
List<Item> items = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
items.add(Item.valueOf(cursor));
|
||||
}
|
||||
// cursor.close();
|
||||
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter();
|
||||
adapter.addAll(items);
|
||||
adapter.notifyDataSetChanged();
|
||||
if (!mIsAlreadySetPosition) {
|
||||
//onAlbumMediaLoad is called many times..
|
||||
mIsAlreadySetPosition = true;
|
||||
Item selected = getIntent().getParcelableExtra(EXTRA_ITEM);
|
||||
int selectedIndex = items.indexOf(selected);
|
||||
mPager.setCurrentItem(selectedIndex, false);
|
||||
mPreviousPos = selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaReset() {
|
||||
|
||||
}
|
||||
}
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.IncapableCause;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.model.SelectedItemCollection;
|
||||
import com.zhihu.matisse.internal.ui.adapter.PreviewPagerAdapter;
|
||||
import com.zhihu.matisse.internal.ui.widget.CheckRadioView;
|
||||
import com.zhihu.matisse.internal.ui.widget.CheckView;
|
||||
import com.zhihu.matisse.internal.ui.widget.IncapableDialog;
|
||||
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
|
||||
import com.zhihu.matisse.internal.utils.Platform;
|
||||
import com.zhihu.matisse.listener.OnFragmentInteractionListener;
|
||||
|
||||
public abstract class BasePreviewActivity extends AppCompatActivity implements View.OnClickListener,
|
||||
ViewPager.OnPageChangeListener, OnFragmentInteractionListener {
|
||||
|
||||
public static final String EXTRA_DEFAULT_BUNDLE = "extra_default_bundle";
|
||||
public static final String EXTRA_RESULT_BUNDLE = "extra_result_bundle";
|
||||
public static final String EXTRA_RESULT_APPLY = "extra_result_apply";
|
||||
public static final String EXTRA_RESULT_ORIGINAL_ENABLE = "extra_result_original_enable";
|
||||
public static final String CHECK_STATE = "checkState";
|
||||
|
||||
protected final SelectedItemCollection mSelectedCollection = new SelectedItemCollection(this);
|
||||
protected SelectionSpec mSpec;
|
||||
protected ViewPager mPager;
|
||||
|
||||
protected PreviewPagerAdapter mAdapter;
|
||||
|
||||
protected CheckView mCheckView;
|
||||
protected TextView mButtonBack;
|
||||
protected TextView mButtonApply;
|
||||
protected TextView mSize;
|
||||
|
||||
protected int mPreviousPos = -1;
|
||||
|
||||
private LinearLayout mOriginalLayout;
|
||||
private CheckRadioView mOriginal;
|
||||
protected boolean mOriginalEnable;
|
||||
|
||||
private FrameLayout mBottomToolbar;
|
||||
private FrameLayout mTopToolbar;
|
||||
private boolean mIsToolbarHide = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setTheme(SelectionSpec.getInstance().themeId);
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!SelectionSpec.getInstance().hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
setContentView(R.layout.activity_media_preview);
|
||||
if (Platform.hasKitKat()) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
|
||||
mSpec = SelectionSpec.getInstance();
|
||||
if (mSpec.needOrientationRestriction()) {
|
||||
setRequestedOrientation(mSpec.orientation);
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mSelectedCollection.onCreate(getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE));
|
||||
mOriginalEnable = getIntent().getBooleanExtra(EXTRA_RESULT_ORIGINAL_ENABLE, false);
|
||||
} else {
|
||||
mSelectedCollection.onCreate(savedInstanceState);
|
||||
mOriginalEnable = savedInstanceState.getBoolean(CHECK_STATE);
|
||||
}
|
||||
mButtonBack = (TextView) findViewById(R.id.button_back);
|
||||
mButtonApply = (TextView) findViewById(R.id.button_apply);
|
||||
mSize = (TextView) findViewById(R.id.size);
|
||||
mButtonBack.setOnClickListener(this);
|
||||
mButtonApply.setOnClickListener(this);
|
||||
|
||||
mPager = (ViewPager) findViewById(R.id.pager);
|
||||
mPager.addOnPageChangeListener(this);
|
||||
mAdapter = new PreviewPagerAdapter(getSupportFragmentManager(), null);
|
||||
mPager.setAdapter(mAdapter);
|
||||
mCheckView = (CheckView) findViewById(R.id.check_view);
|
||||
mCheckView.setCountable(mSpec.countable);
|
||||
mBottomToolbar = findViewById(R.id.bottom_toolbar);
|
||||
mTopToolbar = findViewById(R.id.top_toolbar);
|
||||
|
||||
mCheckView.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Item item = mAdapter.getMediaItem(mPager.getCurrentItem());
|
||||
if (mSelectedCollection.isSelected(item)) {
|
||||
mSelectedCollection.remove(item);
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(CheckView.UNCHECKED);
|
||||
} else {
|
||||
mCheckView.setChecked(false);
|
||||
}
|
||||
} else {
|
||||
if (assertAddSelection(item)) {
|
||||
mSelectedCollection.add(item);
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item));
|
||||
} else {
|
||||
mCheckView.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateApplyButton();
|
||||
|
||||
if (mSpec.onSelectedListener != null) {
|
||||
mSpec.onSelectedListener.onSelected(
|
||||
mSelectedCollection.asListOfUri(), mSelectedCollection.asListOfString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mOriginalLayout = findViewById(R.id.originalLayout);
|
||||
mOriginal = findViewById(R.id.original);
|
||||
mOriginalLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
int count = countOverMaxSize();
|
||||
if (count > 0) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_count, count, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
mOriginalEnable = !mOriginalEnable;
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
if (!mOriginalEnable) {
|
||||
mOriginal.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
|
||||
if (mSpec.onCheckedListener != null) {
|
||||
mSpec.onCheckedListener.onCheck(mOriginalEnable);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateApplyButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
mSelectedCollection.onSaveInstanceState(outState);
|
||||
outState.putBoolean("checkState", mOriginalEnable);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
sendBackResult(false);
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.button_back) {
|
||||
onBackPressed();
|
||||
} else if (v.getId() == R.id.button_apply) {
|
||||
sendBackResult(true);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
if (!mSpec.autoHideToobar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsToolbarHide) {
|
||||
mTopToolbar.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.translationYBy(mTopToolbar.getMeasuredHeight())
|
||||
.start();
|
||||
mBottomToolbar.animate()
|
||||
.translationYBy(-mBottomToolbar.getMeasuredHeight())
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
} else {
|
||||
mTopToolbar.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.translationYBy(-mTopToolbar.getMeasuredHeight())
|
||||
.start();
|
||||
mBottomToolbar.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.translationYBy(mBottomToolbar.getMeasuredHeight())
|
||||
.start();
|
||||
}
|
||||
|
||||
mIsToolbarHide = !mIsToolbarHide;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter();
|
||||
if (mPreviousPos != -1 && mPreviousPos != position) {
|
||||
((PreviewItemFragment) adapter.instantiateItem(mPager, mPreviousPos)).resetView();
|
||||
|
||||
Item item = adapter.getMediaItem(position);
|
||||
if (mSpec.countable) {
|
||||
int checkedNum = mSelectedCollection.checkedNumOf(item);
|
||||
mCheckView.setCheckedNum(checkedNum);
|
||||
if (checkedNum > 0) {
|
||||
mCheckView.setEnabled(true);
|
||||
} else {
|
||||
mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
|
||||
}
|
||||
} else {
|
||||
boolean checked = mSelectedCollection.isSelected(item);
|
||||
mCheckView.setChecked(checked);
|
||||
if (checked) {
|
||||
mCheckView.setEnabled(true);
|
||||
} else {
|
||||
mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
|
||||
}
|
||||
}
|
||||
updateSize(item);
|
||||
}
|
||||
mPreviousPos = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
|
||||
}
|
||||
|
||||
private void updateApplyButton() {
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
if (selectedCount == 0) {
|
||||
mButtonApply.setText(R.string.button_apply_default);
|
||||
mButtonApply.setEnabled(false);
|
||||
} else if (selectedCount == 1 && mSpec.singleSelectionModeEnabled()) {
|
||||
mButtonApply.setText(R.string.button_apply_default);
|
||||
mButtonApply.setEnabled(true);
|
||||
} else {
|
||||
mButtonApply.setEnabled(true);
|
||||
mButtonApply.setText(getString(R.string.button_apply, selectedCount));
|
||||
}
|
||||
|
||||
if (mSpec.originalable) {
|
||||
mOriginalLayout.setVisibility(View.VISIBLE);
|
||||
updateOriginalState();
|
||||
} else {
|
||||
mOriginalLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateOriginalState() {
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
if (!mOriginalEnable) {
|
||||
mOriginal.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
if (countOverMaxSize() > 0) {
|
||||
|
||||
if (mOriginalEnable) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_size, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
|
||||
mOriginal.setChecked(false);
|
||||
mOriginal.setColor(Color.WHITE);
|
||||
mOriginalEnable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int countOverMaxSize() {
|
||||
int count = 0;
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
for (int i = 0; i < selectedCount; i++) {
|
||||
Item item = mSelectedCollection.asList().get(i);
|
||||
if (item.isImage()) {
|
||||
float size = PhotoMetadataUtils.getSizeInMB(item.size);
|
||||
if (size > mSpec.originalMaxSize) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
protected void updateSize(Item item) {
|
||||
if (item.isGif()) {
|
||||
mSize.setVisibility(View.VISIBLE);
|
||||
mSize.setText(PhotoMetadataUtils.getSizeInMB(item.size) + "M");
|
||||
} else {
|
||||
mSize.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (item.isVideo()) {
|
||||
mOriginalLayout.setVisibility(View.GONE);
|
||||
} else if (mSpec.originalable) {
|
||||
mOriginalLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendBackResult(boolean apply) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_RESULT_BUNDLE, mSelectedCollection.getDataWithBundle());
|
||||
intent.putExtra(EXTRA_RESULT_APPLY, apply);
|
||||
intent.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
}
|
||||
|
||||
private boolean assertAddSelection(Item item) {
|
||||
IncapableCause cause = mSelectedCollection.isAcceptable(item);
|
||||
IncapableCause.handleCause(this, cause);
|
||||
return cause == null;
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.model.AlbumMediaCollection;
|
||||
import com.zhihu.matisse.internal.model.SelectedItemCollection;
|
||||
import com.zhihu.matisse.internal.ui.adapter.AlbumMediaAdapter;
|
||||
import com.zhihu.matisse.internal.ui.widget.MediaGridInset;
|
||||
import com.zhihu.matisse.internal.utils.UIUtils;
|
||||
|
||||
public class MediaSelectionFragment extends Fragment implements
|
||||
AlbumMediaCollection.AlbumMediaCallbacks, AlbumMediaAdapter.CheckStateListener,
|
||||
AlbumMediaAdapter.OnMediaClickListener {
|
||||
|
||||
public static final String EXTRA_ALBUM = "extra_album";
|
||||
|
||||
private final AlbumMediaCollection mAlbumMediaCollection = new AlbumMediaCollection();
|
||||
private RecyclerView mRecyclerView;
|
||||
private AlbumMediaAdapter mAdapter;
|
||||
private SelectionProvider mSelectionProvider;
|
||||
private AlbumMediaAdapter.CheckStateListener mCheckStateListener;
|
||||
private AlbumMediaAdapter.OnMediaClickListener mOnMediaClickListener;
|
||||
|
||||
public static MediaSelectionFragment newInstance(Album album) {
|
||||
MediaSelectionFragment fragment = new MediaSelectionFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(EXTRA_ALBUM, album);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof SelectionProvider) {
|
||||
mSelectionProvider = (SelectionProvider) context;
|
||||
} else {
|
||||
throw new IllegalStateException("Context must implement SelectionProvider.");
|
||||
}
|
||||
if (context instanceof AlbumMediaAdapter.CheckStateListener) {
|
||||
mCheckStateListener = (AlbumMediaAdapter.CheckStateListener) context;
|
||||
}
|
||||
if (context instanceof AlbumMediaAdapter.OnMediaClickListener) {
|
||||
mOnMediaClickListener = (AlbumMediaAdapter.OnMediaClickListener) context;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_media_selection, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
Album album = getArguments().getParcelable(EXTRA_ALBUM);
|
||||
|
||||
mAdapter = new AlbumMediaAdapter(getContext(),
|
||||
mSelectionProvider.provideSelectedItemCollection(), mRecyclerView);
|
||||
mAdapter.registerCheckStateListener(this);
|
||||
mAdapter.registerOnMediaClickListener(this);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
|
||||
int spanCount;
|
||||
SelectionSpec selectionSpec = SelectionSpec.getInstance();
|
||||
if (selectionSpec.gridExpectedSize > 0) {
|
||||
spanCount = UIUtils.spanCount(getContext(), selectionSpec.gridExpectedSize);
|
||||
} else {
|
||||
spanCount = selectionSpec.spanCount;
|
||||
}
|
||||
mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), spanCount));
|
||||
|
||||
int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
|
||||
mRecyclerView.addItemDecoration(new MediaGridInset(spanCount, spacing, false));
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
mAlbumMediaCollection.onCreate(getActivity(), this);
|
||||
mAlbumMediaCollection.load(album, selectionSpec.capture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mAlbumMediaCollection.onDestroy();
|
||||
}
|
||||
|
||||
public void refreshMediaGrid() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void refreshSelection() {
|
||||
mAdapter.refreshSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaLoad(Cursor cursor) {
|
||||
mAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaReset() {
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
// notify outer Activity that check state changed
|
||||
if (mCheckStateListener != null) {
|
||||
mCheckStateListener.onUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Album album, Item item, int adapterPosition) {
|
||||
if (mOnMediaClickListener != null) {
|
||||
mOnMediaClickListener.onMediaClick((Album) getArguments().getParcelable(EXTRA_ALBUM),
|
||||
item, adapterPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SelectionProvider {
|
||||
SelectedItemCollection provideSelectedItemCollection();
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Point;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
|
||||
import com.zhihu.matisse.listener.OnFragmentInteractionListener;
|
||||
|
||||
import it.sephiroth.android.library.imagezoom.ImageViewTouch;
|
||||
import it.sephiroth.android.library.imagezoom.ImageViewTouchBase;
|
||||
|
||||
public class PreviewItemFragment extends Fragment {
|
||||
|
||||
private static final String ARGS_ITEM = "args_item";
|
||||
private OnFragmentInteractionListener mListener;
|
||||
|
||||
public static PreviewItemFragment newInstance(Item item) {
|
||||
PreviewItemFragment fragment = new PreviewItemFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ARGS_ITEM, item);
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_preview_item, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final Item item = getArguments().getParcelable(ARGS_ITEM);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
View videoPlayButton = view.findViewById(R.id.video_play_button);
|
||||
if (item.isVideo()) {
|
||||
videoPlayButton.setVisibility(View.VISIBLE);
|
||||
videoPlayButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(item.uri, "video/*");
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getContext(), R.string.error_no_video_activity, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
videoPlayButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
ImageViewTouch image = (ImageViewTouch) view.findViewById(R.id.image_view);
|
||||
image.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);
|
||||
|
||||
image.setSingleTapListener(new ImageViewTouch.OnImageViewTouchSingleTapListener() {
|
||||
@Override
|
||||
public void onSingleTapConfirmed() {
|
||||
if (mListener != null) {
|
||||
mListener.onClick();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Point size = PhotoMetadataUtils.getBitmapSize(item.getContentUri(), getActivity());
|
||||
if (item.isGif()) {
|
||||
SelectionSpec.getInstance().imageEngine.loadGifImage(getContext(), size.x, size.y, image,
|
||||
item.getContentUri());
|
||||
} else {
|
||||
SelectionSpec.getInstance().imageEngine.loadImage(getContext(), size.x, size.y, image,
|
||||
item.getContentUri());
|
||||
}
|
||||
}
|
||||
|
||||
public void resetView() {
|
||||
if (getView() != null) {
|
||||
((ImageViewTouch) getView().findViewById(R.id.image_view)).resetMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof OnFragmentInteractionListener) {
|
||||
mListener = (OnFragmentInteractionListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement OnFragmentInteractionListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.model.SelectedItemCollection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SelectedPreviewActivity extends BasePreviewActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!SelectionSpec.getInstance().hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE);
|
||||
List<Item> selected = bundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION);
|
||||
mAdapter.addAll(selected);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(1);
|
||||
} else {
|
||||
mCheckView.setChecked(true);
|
||||
}
|
||||
mPreviousPos = 0;
|
||||
updateSize(selected.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.entity.IncapableCause;
|
||||
import com.zhihu.matisse.internal.model.SelectedItemCollection;
|
||||
import com.zhihu.matisse.internal.ui.widget.CheckView;
|
||||
import com.zhihu.matisse.internal.ui.widget.MediaGrid;
|
||||
|
||||
public class AlbumMediaAdapter extends
|
||||
RecyclerViewCursorAdapter<RecyclerView.ViewHolder> implements
|
||||
MediaGrid.OnMediaGridClickListener {
|
||||
|
||||
private static final int VIEW_TYPE_CAPTURE = 0x01;
|
||||
private static final int VIEW_TYPE_MEDIA = 0x02;
|
||||
private final SelectedItemCollection mSelectedCollection;
|
||||
private final Drawable mPlaceholder;
|
||||
private SelectionSpec mSelectionSpec;
|
||||
private CheckStateListener mCheckStateListener;
|
||||
private OnMediaClickListener mOnMediaClickListener;
|
||||
private RecyclerView mRecyclerView;
|
||||
private int mImageResize;
|
||||
|
||||
public AlbumMediaAdapter(Context context, SelectedItemCollection selectedCollection, RecyclerView recyclerView) {
|
||||
super(null);
|
||||
mSelectionSpec = SelectionSpec.getInstance();
|
||||
mSelectedCollection = selectedCollection;
|
||||
|
||||
TypedArray ta = context.getTheme().obtainStyledAttributes(new int[]{R.attr.item_placeholder});
|
||||
mPlaceholder = ta.getDrawable(0);
|
||||
ta.recycle();
|
||||
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (viewType == VIEW_TYPE_CAPTURE) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.photo_capture_item, parent, false);
|
||||
CaptureViewHolder holder = new CaptureViewHolder(v);
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getContext() instanceof OnPhotoCapture) {
|
||||
((OnPhotoCapture) v.getContext()).capture();
|
||||
}
|
||||
}
|
||||
});
|
||||
return holder;
|
||||
} else if (viewType == VIEW_TYPE_MEDIA) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.media_grid_item, parent, false);
|
||||
return new MediaViewHolder(v);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindViewHolder(final RecyclerView.ViewHolder holder, Cursor cursor) {
|
||||
if (holder instanceof CaptureViewHolder) {
|
||||
CaptureViewHolder captureViewHolder = (CaptureViewHolder) holder;
|
||||
Drawable[] drawables = captureViewHolder.mHint.getCompoundDrawables();
|
||||
TypedArray ta = holder.itemView.getContext().getTheme().obtainStyledAttributes(
|
||||
new int[]{R.attr.capture_textColor});
|
||||
int color = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
|
||||
for (int i = 0; i < drawables.length; i++) {
|
||||
Drawable drawable = drawables[i];
|
||||
if (drawable != null) {
|
||||
final Drawable.ConstantState state = drawable.getConstantState();
|
||||
if (state == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Drawable newDrawable = state.newDrawable().mutate();
|
||||
newDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
newDrawable.setBounds(drawable.getBounds());
|
||||
drawables[i] = newDrawable;
|
||||
}
|
||||
}
|
||||
captureViewHolder.mHint.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]);
|
||||
} else if (holder instanceof MediaViewHolder) {
|
||||
MediaViewHolder mediaViewHolder = (MediaViewHolder) holder;
|
||||
|
||||
final Item item = Item.valueOf(cursor);
|
||||
mediaViewHolder.mMediaGrid.preBindMedia(new MediaGrid.PreBindInfo(
|
||||
getImageResize(mediaViewHolder.mMediaGrid.getContext()),
|
||||
mPlaceholder,
|
||||
mSelectionSpec.countable,
|
||||
holder
|
||||
));
|
||||
mediaViewHolder.mMediaGrid.bindMedia(item);
|
||||
mediaViewHolder.mMediaGrid.setOnMediaGridClickListener(this);
|
||||
setCheckStatus(item, mediaViewHolder.mMediaGrid);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCheckStatus(Item item, MediaGrid mediaGrid) {
|
||||
if (mSelectionSpec.countable) {
|
||||
int checkedNum = mSelectedCollection.checkedNumOf(item);
|
||||
if (checkedNum > 0) {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setCheckedNum(checkedNum);
|
||||
} else {
|
||||
if (mSelectedCollection.maxSelectableReached()) {
|
||||
mediaGrid.setCheckEnabled(false);
|
||||
mediaGrid.setCheckedNum(CheckView.UNCHECKED);
|
||||
} else {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setCheckedNum(checkedNum);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean selected = mSelectedCollection.isSelected(item);
|
||||
if (selected) {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setChecked(true);
|
||||
} else {
|
||||
if (mSelectedCollection.maxSelectableReached()) {
|
||||
mediaGrid.setCheckEnabled(false);
|
||||
mediaGrid.setChecked(false);
|
||||
} else {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder) {
|
||||
if (mSelectionSpec.showPreview) {
|
||||
if (mOnMediaClickListener != null) {
|
||||
mOnMediaClickListener.onMediaClick(null, item, holder.getAdapterPosition());
|
||||
}
|
||||
} else {
|
||||
updateSelectedItem(item, holder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder) {
|
||||
updateSelectedItem(item, holder);
|
||||
}
|
||||
|
||||
private void updateSelectedItem(Item item, RecyclerView.ViewHolder holder) {
|
||||
if (mSelectionSpec.countable) {
|
||||
int checkedNum = mSelectedCollection.checkedNumOf(item);
|
||||
if (checkedNum == CheckView.UNCHECKED) {
|
||||
if (assertAddSelection(holder.itemView.getContext(), item)) {
|
||||
mSelectedCollection.add(item);
|
||||
notifyCheckStateChanged();
|
||||
}
|
||||
} else {
|
||||
mSelectedCollection.remove(item);
|
||||
notifyCheckStateChanged();
|
||||
}
|
||||
} else {
|
||||
if (mSelectedCollection.isSelected(item)) {
|
||||
mSelectedCollection.remove(item);
|
||||
notifyCheckStateChanged();
|
||||
} else {
|
||||
if (assertAddSelection(holder.itemView.getContext(), item)) {
|
||||
mSelectedCollection.add(item);
|
||||
notifyCheckStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCheckStateChanged() {
|
||||
notifyDataSetChanged();
|
||||
if (mCheckStateListener != null) {
|
||||
mCheckStateListener.onUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position, Cursor cursor) {
|
||||
return Item.valueOf(cursor).isCapture() ? VIEW_TYPE_CAPTURE : VIEW_TYPE_MEDIA;
|
||||
}
|
||||
|
||||
private boolean assertAddSelection(Context context, Item item) {
|
||||
IncapableCause cause = mSelectedCollection.isAcceptable(item);
|
||||
IncapableCause.handleCause(context, cause);
|
||||
return cause == null;
|
||||
}
|
||||
|
||||
|
||||
public void registerCheckStateListener(CheckStateListener listener) {
|
||||
mCheckStateListener = listener;
|
||||
}
|
||||
|
||||
public void unregisterCheckStateListener() {
|
||||
mCheckStateListener = null;
|
||||
}
|
||||
|
||||
public void registerOnMediaClickListener(OnMediaClickListener listener) {
|
||||
mOnMediaClickListener = listener;
|
||||
}
|
||||
|
||||
public void unregisterOnMediaClickListener() {
|
||||
mOnMediaClickListener = null;
|
||||
}
|
||||
|
||||
public void refreshSelection() {
|
||||
GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
|
||||
int first = layoutManager.findFirstVisibleItemPosition();
|
||||
int last = layoutManager.findLastVisibleItemPosition();
|
||||
if (first == -1 || last == -1) {
|
||||
return;
|
||||
}
|
||||
Cursor cursor = getCursor();
|
||||
for (int i = first; i <= last; i++) {
|
||||
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(first);
|
||||
if (holder instanceof MediaViewHolder) {
|
||||
if (cursor.moveToPosition(i)) {
|
||||
setCheckStatus(Item.valueOf(cursor), ((MediaViewHolder) holder).mMediaGrid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getImageResize(Context context) {
|
||||
if (mImageResize == 0) {
|
||||
RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
|
||||
int spanCount = ((GridLayoutManager) lm).getSpanCount();
|
||||
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
int availableWidth = screenWidth - context.getResources().getDimensionPixelSize(
|
||||
R.dimen.media_grid_spacing) * (spanCount - 1);
|
||||
mImageResize = availableWidth / spanCount;
|
||||
mImageResize = (int) (mImageResize * mSelectionSpec.thumbnailScale);
|
||||
}
|
||||
return mImageResize;
|
||||
}
|
||||
|
||||
public interface CheckStateListener {
|
||||
void onUpdate();
|
||||
}
|
||||
|
||||
public interface OnMediaClickListener {
|
||||
void onMediaClick(Album album, Item item, int adapterPosition);
|
||||
}
|
||||
|
||||
public interface OnPhotoCapture {
|
||||
void capture();
|
||||
}
|
||||
|
||||
private static class MediaViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private MediaGrid mMediaGrid;
|
||||
|
||||
MediaViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
mMediaGrid = (MediaGrid) itemView;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CaptureViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private TextView mHint;
|
||||
|
||||
CaptureViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
mHint = (TextView) itemView.findViewById(R.id.hint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
|
||||
public class AlbumsAdapter extends CursorAdapter {
|
||||
|
||||
private final Drawable mPlaceholder;
|
||||
|
||||
public AlbumsAdapter(Context context, Cursor c, boolean autoRequery) {
|
||||
super(context, c, autoRequery);
|
||||
|
||||
TypedArray ta = context.getTheme().obtainStyledAttributes(
|
||||
new int[]{R.attr.album_thumbnail_placeholder});
|
||||
mPlaceholder = ta.getDrawable(0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public AlbumsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
TypedArray ta = context.getTheme().obtainStyledAttributes(
|
||||
new int[]{R.attr.album_thumbnail_placeholder});
|
||||
mPlaceholder = ta.getDrawable(0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.album_list_item, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
Album album = Album.valueOf(cursor);
|
||||
((TextView) view.findViewById(R.id.album_name)).setText(album.getDisplayName(context));
|
||||
((TextView) view.findViewById(R.id.album_media_count)).setText(String.valueOf(album.getCount()));
|
||||
|
||||
// do not need to load animated Gif
|
||||
SelectionSpec.getInstance().imageEngine.loadThumbnail(context, context.getResources().getDimensionPixelSize(R
|
||||
.dimen.media_grid_size), mPlaceholder,
|
||||
(ImageView) view.findViewById(R.id.album_cover), album.getCoverUri());
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.adapter;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.ui.PreviewItemFragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PreviewPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private ArrayList<Item> mItems = new ArrayList<>();
|
||||
private OnPrimaryItemSetListener mListener;
|
||||
|
||||
public PreviewPagerAdapter(FragmentManager manager, OnPrimaryItemSetListener listener) {
|
||||
super(manager);
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return PreviewItemFragment.newInstance(mItems.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||
super.setPrimaryItem(container, position, object);
|
||||
if (mListener != null) {
|
||||
mListener.onPrimaryItemSet(position);
|
||||
}
|
||||
}
|
||||
|
||||
public Item getMediaItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
public void addAll(List<Item> items) {
|
||||
mItems.addAll(items);
|
||||
}
|
||||
|
||||
interface OnPrimaryItemSetListener {
|
||||
|
||||
void onPrimaryItemSet(int position);
|
||||
}
|
||||
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.adapter;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class RecyclerViewCursorAdapter<VH extends RecyclerView.ViewHolder> extends
|
||||
RecyclerView.Adapter<VH> {
|
||||
|
||||
private Cursor mCursor;
|
||||
private int mRowIDColumn;
|
||||
|
||||
RecyclerViewCursorAdapter(Cursor c) {
|
||||
setHasStableIds(true);
|
||||
swapCursor(c);
|
||||
}
|
||||
|
||||
protected abstract void onBindViewHolder(VH holder, Cursor cursor);
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(VH holder, int position) {
|
||||
if (!isDataValid(mCursor)) {
|
||||
throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
|
||||
}
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + position
|
||||
+ " when trying to bind view holder");
|
||||
}
|
||||
|
||||
onBindViewHolder(holder, mCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + position
|
||||
+ " when trying to get item view type.");
|
||||
}
|
||||
return getItemViewType(position, mCursor);
|
||||
}
|
||||
|
||||
protected abstract int getItemViewType(int position, Cursor cursor);
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (isDataValid(mCursor)) {
|
||||
return mCursor.getCount();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (!isDataValid(mCursor)) {
|
||||
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
|
||||
}
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + position
|
||||
+ " when trying to get an item id");
|
||||
}
|
||||
|
||||
return mCursor.getLong(mRowIDColumn);
|
||||
}
|
||||
|
||||
public void swapCursor(Cursor newCursor) {
|
||||
if (newCursor == mCursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newCursor != null) {
|
||||
mCursor = newCursor;
|
||||
mRowIDColumn = mCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID);
|
||||
// notify the observers about the new cursor
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
notifyItemRangeRemoved(0, getItemCount());
|
||||
mCursor = null;
|
||||
mRowIDColumn = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public Cursor getCursor() {
|
||||
return mCursor;
|
||||
}
|
||||
|
||||
private boolean isDataValid(Cursor cursor) {
|
||||
return cursor != null && !cursor.isClosed();
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.ListPopupWindow;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.utils.Platform;
|
||||
|
||||
public class AlbumsSpinner {
|
||||
|
||||
private static final int MAX_SHOWN_COUNT = 6;
|
||||
private CursorAdapter mAdapter;
|
||||
private TextView mSelected;
|
||||
private ListPopupWindow mListPopupWindow;
|
||||
private AdapterView.OnItemSelectedListener mOnItemSelectedListener;
|
||||
|
||||
public AlbumsSpinner(@NonNull Context context) {
|
||||
mListPopupWindow = new ListPopupWindow(context, null, R.attr.listPopupWindowStyle);
|
||||
mListPopupWindow.setModal(true);
|
||||
float density = context.getResources().getDisplayMetrics().density;
|
||||
mListPopupWindow.setContentWidth((int) (216 * density));
|
||||
mListPopupWindow.setHorizontalOffset((int) (16 * density));
|
||||
mListPopupWindow.setVerticalOffset((int) (-48 * density));
|
||||
|
||||
mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
AlbumsSpinner.this.onItemSelected(parent.getContext(), position);
|
||||
if (mOnItemSelectedListener != null) {
|
||||
mOnItemSelectedListener.onItemSelected(parent, view, position, id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
|
||||
mOnItemSelectedListener = listener;
|
||||
}
|
||||
|
||||
public void setSelection(Context context, int position) {
|
||||
mListPopupWindow.setSelection(position);
|
||||
onItemSelected(context, position);
|
||||
}
|
||||
|
||||
private void onItemSelected(Context context, int position) {
|
||||
mListPopupWindow.dismiss();
|
||||
Cursor cursor = mAdapter.getCursor();
|
||||
cursor.moveToPosition(position);
|
||||
Album album = Album.valueOf(cursor);
|
||||
String displayName = album.getDisplayName(context);
|
||||
if (mSelected.getVisibility() == View.VISIBLE) {
|
||||
mSelected.setText(displayName);
|
||||
} else {
|
||||
if (Platform.hasICS()) {
|
||||
mSelected.setAlpha(0.0f);
|
||||
mSelected.setVisibility(View.VISIBLE);
|
||||
mSelected.setText(displayName);
|
||||
mSelected.animate().alpha(1.0f).setDuration(context.getResources().getInteger(
|
||||
android.R.integer.config_longAnimTime)).start();
|
||||
} else {
|
||||
mSelected.setVisibility(View.VISIBLE);
|
||||
mSelected.setText(displayName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setAdapter(CursorAdapter adapter) {
|
||||
mListPopupWindow.setAdapter(adapter);
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
public void setSelectedTextView(TextView textView) {
|
||||
mSelected = textView;
|
||||
// tint dropdown arrow icon
|
||||
Drawable[] drawables = mSelected.getCompoundDrawables();
|
||||
Drawable right = drawables[2];
|
||||
TypedArray ta = mSelected.getContext().getTheme().obtainStyledAttributes(
|
||||
new int[]{R.attr.album_element_color});
|
||||
int color = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
right.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
mSelected.setVisibility(View.GONE);
|
||||
mSelected.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int itemHeight = v.getResources().getDimensionPixelSize(R.dimen.album_item_height);
|
||||
mListPopupWindow.setHeight(
|
||||
mAdapter.getCount() > MAX_SHOWN_COUNT ? itemHeight * MAX_SHOWN_COUNT
|
||||
: itemHeight * mAdapter.getCount());
|
||||
mListPopupWindow.show();
|
||||
}
|
||||
});
|
||||
mSelected.setOnTouchListener(mListPopupWindow.createDragToOpenListener(mSelected));
|
||||
}
|
||||
|
||||
public void setPopupAnchorView(View view) {
|
||||
mListPopupWindow.setAnchorView(view);
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
|
||||
public class CheckRadioView extends AppCompatImageView {
|
||||
|
||||
private Drawable mDrawable;
|
||||
|
||||
private int mSelectedColor;
|
||||
private int mUnSelectUdColor;
|
||||
|
||||
public CheckRadioView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CheckRadioView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mSelectedColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_item_checkCircle_backgroundColor,
|
||||
getContext().getTheme());
|
||||
mUnSelectUdColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_check_original_radio_disable,
|
||||
getContext().getTheme());
|
||||
setChecked(false);
|
||||
}
|
||||
|
||||
public void setChecked(boolean enable) {
|
||||
if (enable) {
|
||||
setImageResource(R.drawable.ic_preview_radio_on);
|
||||
mDrawable = getDrawable();
|
||||
mDrawable.setColorFilter(mSelectedColor, PorterDuff.Mode.SRC_IN);
|
||||
} else {
|
||||
setImageResource(R.drawable.ic_preview_radio_off);
|
||||
mDrawable = getDrawable();
|
||||
mDrawable.setColorFilter(mUnSelectUdColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mDrawable == null) {
|
||||
mDrawable = getDrawable();
|
||||
}
|
||||
mDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
|
||||
public class CheckView extends View {
|
||||
|
||||
public static final int UNCHECKED = Integer.MIN_VALUE;
|
||||
private static final float STROKE_WIDTH = 3.0f; // dp
|
||||
private static final float SHADOW_WIDTH = 6.0f; // dp
|
||||
private static final int SIZE = 48; // dp
|
||||
private static final float STROKE_RADIUS = 11.5f; // dp
|
||||
private static final float BG_RADIUS = 11.0f; // dp
|
||||
private static final int CONTENT_SIZE = 16; // dp
|
||||
private boolean mCountable;
|
||||
private boolean mChecked;
|
||||
private int mCheckedNum;
|
||||
private Paint mStrokePaint;
|
||||
private Paint mBackgroundPaint;
|
||||
private TextPaint mTextPaint;
|
||||
private Paint mShadowPaint;
|
||||
private Drawable mCheckDrawable;
|
||||
private float mDensity;
|
||||
private Rect mCheckRect;
|
||||
private boolean mEnabled = true;
|
||||
|
||||
public CheckView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public CheckView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public CheckView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// fixed size 48dp x 48dp
|
||||
int sizeSpec = MeasureSpec.makeMeasureSpec((int) (SIZE * mDensity), MeasureSpec.EXACTLY);
|
||||
super.onMeasure(sizeSpec, sizeSpec);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mDensity = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
mStrokePaint = new Paint();
|
||||
mStrokePaint.setAntiAlias(true);
|
||||
mStrokePaint.setStyle(Paint.Style.STROKE);
|
||||
mStrokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
|
||||
mStrokePaint.setStrokeWidth(STROKE_WIDTH * mDensity);
|
||||
TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{R.attr.item_checkCircle_borderColor});
|
||||
int defaultColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_item_checkCircle_borderColor,
|
||||
getContext().getTheme());
|
||||
int color = ta.getColor(0, defaultColor);
|
||||
ta.recycle();
|
||||
mStrokePaint.setColor(color);
|
||||
|
||||
mCheckDrawable = ResourcesCompat.getDrawable(context.getResources(),
|
||||
R.drawable.ic_check_white_18dp, context.getTheme());
|
||||
}
|
||||
|
||||
public void setChecked(boolean checked) {
|
||||
if (mCountable) {
|
||||
throw new IllegalStateException("CheckView is countable, call setCheckedNum() instead.");
|
||||
}
|
||||
mChecked = checked;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setCountable(boolean countable) {
|
||||
mCountable = countable;
|
||||
}
|
||||
|
||||
public void setCheckedNum(int checkedNum) {
|
||||
if (!mCountable) {
|
||||
throw new IllegalStateException("CheckView is not countable, call setChecked() instead.");
|
||||
}
|
||||
if (checkedNum != UNCHECKED && checkedNum <= 0) {
|
||||
throw new IllegalArgumentException("checked num can't be negative.");
|
||||
}
|
||||
mCheckedNum = checkedNum;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (mEnabled != enabled) {
|
||||
mEnabled = enabled;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
// draw outer and inner shadow
|
||||
initShadowPaint();
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
(STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint);
|
||||
|
||||
// draw white stroke
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
STROKE_RADIUS * mDensity, mStrokePaint);
|
||||
|
||||
// draw content
|
||||
if (mCountable) {
|
||||
if (mCheckedNum != UNCHECKED) {
|
||||
initBackgroundPaint();
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
BG_RADIUS * mDensity, mBackgroundPaint);
|
||||
initTextPaint();
|
||||
String text = String.valueOf(mCheckedNum);
|
||||
int baseX = (int) (canvas.getWidth() - mTextPaint.measureText(text)) / 2;
|
||||
int baseY = (int) (canvas.getHeight() - mTextPaint.descent() - mTextPaint.ascent()) / 2;
|
||||
canvas.drawText(text, baseX, baseY, mTextPaint);
|
||||
}
|
||||
} else {
|
||||
if (mChecked) {
|
||||
initBackgroundPaint();
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
BG_RADIUS * mDensity, mBackgroundPaint);
|
||||
|
||||
mCheckDrawable.setBounds(getCheckRect());
|
||||
mCheckDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
// enable hint
|
||||
setAlpha(mEnabled ? 1.0f : 0.5f);
|
||||
}
|
||||
|
||||
private void initShadowPaint() {
|
||||
if (mShadowPaint == null) {
|
||||
mShadowPaint = new Paint();
|
||||
mShadowPaint.setAntiAlias(true);
|
||||
// all in dp
|
||||
float outerRadius = STROKE_RADIUS + STROKE_WIDTH / 2;
|
||||
float innerRadius = outerRadius - STROKE_WIDTH;
|
||||
float gradientRadius = outerRadius + SHADOW_WIDTH;
|
||||
float stop0 = (innerRadius - SHADOW_WIDTH) / gradientRadius;
|
||||
float stop1 = innerRadius / gradientRadius;
|
||||
float stop2 = outerRadius / gradientRadius;
|
||||
float stop3 = 1.0f;
|
||||
mShadowPaint.setShader(
|
||||
new RadialGradient((float) SIZE * mDensity / 2,
|
||||
(float) SIZE * mDensity / 2,
|
||||
gradientRadius * mDensity,
|
||||
new int[]{Color.parseColor("#00000000"), Color.parseColor("#0D000000"),
|
||||
Color.parseColor("#0D000000"), Color.parseColor("#00000000")},
|
||||
new float[]{stop0, stop1, stop2, stop3},
|
||||
Shader.TileMode.CLAMP));
|
||||
}
|
||||
}
|
||||
|
||||
private void initBackgroundPaint() {
|
||||
if (mBackgroundPaint == null) {
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
mBackgroundPaint.setStyle(Paint.Style.FILL);
|
||||
TypedArray ta = getContext().getTheme()
|
||||
.obtainStyledAttributes(new int[]{R.attr.item_checkCircle_backgroundColor});
|
||||
int defaultColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_item_checkCircle_backgroundColor,
|
||||
getContext().getTheme());
|
||||
int color = ta.getColor(0, defaultColor);
|
||||
ta.recycle();
|
||||
mBackgroundPaint.setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void initTextPaint() {
|
||||
if (mTextPaint == null) {
|
||||
mTextPaint = new TextPaint();
|
||||
mTextPaint.setAntiAlias(true);
|
||||
mTextPaint.setColor(Color.WHITE);
|
||||
mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
|
||||
mTextPaint.setTextSize(12.0f * mDensity);
|
||||
}
|
||||
}
|
||||
|
||||
// rect for drawing checked number or mark
|
||||
private Rect getCheckRect() {
|
||||
if (mCheckRect == null) {
|
||||
int rectPadding = (int) (SIZE * mDensity / 2 - CONTENT_SIZE * mDensity / 2);
|
||||
mCheckRect = new Rect(rectPadding, rectPadding,
|
||||
(int) (SIZE * mDensity - rectPadding), (int) (SIZE * mDensity - rectPadding));
|
||||
}
|
||||
|
||||
return mCheckRect;
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
|
||||
public class IncapableDialog extends DialogFragment {
|
||||
|
||||
public static final String EXTRA_TITLE = "extra_title";
|
||||
public static final String EXTRA_MESSAGE = "extra_message";
|
||||
|
||||
public static IncapableDialog newInstance(String title, String message) {
|
||||
IncapableDialog dialog = new IncapableDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(EXTRA_TITLE, title);
|
||||
args.putString(EXTRA_MESSAGE, message);
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
String title = getArguments().getString(EXTRA_TITLE);
|
||||
String message = getArguments().getString(EXTRA_MESSAGE);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title);
|
||||
}
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
builder.setMessage(message);
|
||||
}
|
||||
builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
|
||||
public class MediaGrid extends SquareFrameLayout implements View.OnClickListener {
|
||||
|
||||
private ImageView mThumbnail;
|
||||
private CheckView mCheckView;
|
||||
private ImageView mGifTag;
|
||||
private TextView mVideoDuration;
|
||||
|
||||
private Item mMedia;
|
||||
private PreBindInfo mPreBindInfo;
|
||||
private OnMediaGridClickListener mListener;
|
||||
|
||||
public MediaGrid(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public MediaGrid(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
LayoutInflater.from(context).inflate(R.layout.media_grid_content, this, true);
|
||||
|
||||
mThumbnail = (ImageView) findViewById(R.id.media_thumbnail);
|
||||
mCheckView = (CheckView) findViewById(R.id.check_view);
|
||||
mGifTag = (ImageView) findViewById(R.id.gif);
|
||||
mVideoDuration = (TextView) findViewById(R.id.video_duration);
|
||||
|
||||
mThumbnail.setOnClickListener(this);
|
||||
mCheckView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mListener != null) {
|
||||
if (v == mThumbnail) {
|
||||
mListener.onThumbnailClicked(mThumbnail, mMedia, mPreBindInfo.mViewHolder);
|
||||
} else if (v == mCheckView) {
|
||||
mListener.onCheckViewClicked(mCheckView, mMedia, mPreBindInfo.mViewHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void preBindMedia(PreBindInfo info) {
|
||||
mPreBindInfo = info;
|
||||
}
|
||||
|
||||
public void bindMedia(Item item) {
|
||||
mMedia = item;
|
||||
setGifTag();
|
||||
initCheckView();
|
||||
setImage();
|
||||
setVideoDuration();
|
||||
}
|
||||
|
||||
public Item getMedia() {
|
||||
return mMedia;
|
||||
}
|
||||
|
||||
private void setGifTag() {
|
||||
mGifTag.setVisibility(mMedia.isGif() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void initCheckView() {
|
||||
mCheckView.setCountable(mPreBindInfo.mCheckViewCountable);
|
||||
}
|
||||
|
||||
public void setCheckEnabled(boolean enabled) {
|
||||
mCheckView.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setCheckedNum(int checkedNum) {
|
||||
mCheckView.setCheckedNum(checkedNum);
|
||||
}
|
||||
|
||||
public void setChecked(boolean checked) {
|
||||
mCheckView.setChecked(checked);
|
||||
}
|
||||
|
||||
private void setImage() {
|
||||
if (mMedia.isGif()) {
|
||||
SelectionSpec.getInstance().imageEngine.loadGifThumbnail(getContext(), mPreBindInfo.mResize,
|
||||
mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri());
|
||||
} else {
|
||||
SelectionSpec.getInstance().imageEngine.loadThumbnail(getContext(), mPreBindInfo.mResize,
|
||||
mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri());
|
||||
}
|
||||
}
|
||||
|
||||
private void setVideoDuration() {
|
||||
if (mMedia.isVideo()) {
|
||||
mVideoDuration.setVisibility(VISIBLE);
|
||||
mVideoDuration.setText(DateUtils.formatElapsedTime(mMedia.duration / 1000));
|
||||
} else {
|
||||
mVideoDuration.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnMediaGridClickListener(OnMediaGridClickListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void removeOnMediaGridClickListener() {
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public interface OnMediaGridClickListener {
|
||||
|
||||
void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder);
|
||||
|
||||
void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder);
|
||||
}
|
||||
|
||||
public static class PreBindInfo {
|
||||
int mResize;
|
||||
Drawable mPlaceholder;
|
||||
boolean mCheckViewCountable;
|
||||
RecyclerView.ViewHolder mViewHolder;
|
||||
|
||||
public PreBindInfo(int resize, Drawable placeholder, boolean checkViewCountable,
|
||||
RecyclerView.ViewHolder viewHolder) {
|
||||
mResize = resize;
|
||||
mPlaceholder = placeholder;
|
||||
mCheckViewCountable = checkViewCountable;
|
||||
mViewHolder = viewHolder;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
public class MediaGridInset extends RecyclerView.ItemDecoration {
|
||||
|
||||
private int mSpanCount;
|
||||
private int mSpacing;
|
||||
private boolean mIncludeEdge;
|
||||
|
||||
public MediaGridInset(int spanCount, int spacing, boolean includeEdge) {
|
||||
this.mSpanCount = spanCount;
|
||||
this.mSpacing = spacing;
|
||||
this.mIncludeEdge = includeEdge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
RecyclerView.State state) {
|
||||
int position = parent.getChildAdapterPosition(view); // item position
|
||||
int column = position % mSpanCount; // item column
|
||||
|
||||
if (mIncludeEdge) {
|
||||
// spacing - column * ((1f / spanCount) * spacing)
|
||||
outRect.left = mSpacing - column * mSpacing / mSpanCount;
|
||||
// (column + 1) * ((1f / spanCount) * spacing)
|
||||
outRect.right = (column + 1) * mSpacing / mSpanCount;
|
||||
|
||||
if (position < mSpanCount) { // top edge
|
||||
outRect.top = mSpacing;
|
||||
}
|
||||
outRect.bottom = mSpacing; // item bottom
|
||||
} else {
|
||||
// column * ((1f / spanCount) * spacing)
|
||||
outRect.left = column * mSpacing / mSpanCount;
|
||||
// spacing - (column + 1) * ((1f / spanCount) * spacing)
|
||||
outRect.right = mSpacing - (column + 1) * mSpacing / mSpanCount;
|
||||
if (position >= mSpanCount) {
|
||||
outRect.top = mSpacing; // item top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import it.sephiroth.android.library.imagezoom.ImageViewTouch;
|
||||
|
||||
public class PreviewViewPager extends ViewPager {
|
||||
|
||||
public PreviewViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
|
||||
if (v instanceof ImageViewTouch) {
|
||||
return ((ImageViewTouch) v).canScroll(dx) || super.canScroll(v, checkV, dx, x, y);
|
||||
}
|
||||
return super.canScroll(v, checkV, dx, x, y);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class RoundedRectangleImageView extends AppCompatImageView {
|
||||
|
||||
private float mRadius; // dp
|
||||
private Path mRoundedRectPath;
|
||||
private RectF mRectF;
|
||||
|
||||
public RoundedRectangleImageView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public RoundedRectangleImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public RoundedRectangleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
float density = context.getResources().getDisplayMetrics().density;
|
||||
mRadius = 2.0f * density;
|
||||
mRoundedRectPath = new Path();
|
||||
mRectF = new RectF();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
mRectF.set(0.0f, 0.0f, getMeasuredWidth(), getMeasuredHeight());
|
||||
mRoundedRectPath.addRoundRect(mRectF, mRadius, mRadius, Path.Direction.CW);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
canvas.clipPath(mRoundedRectPath);
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class SquareFrameLayout extends FrameLayout {
|
||||
|
||||
public SquareFrameLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SquareFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.utils;
|
||||
|
||||
import android.media.ExifInterface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Bug fixture for ExifInterface constructor.
|
||||
*/
|
||||
final class ExifInterfaceCompat {
|
||||
private static final String TAG = ExifInterfaceCompat.class.getSimpleName();
|
||||
private static final int EXIF_DEGREE_FALLBACK_VALUE = -1;
|
||||
|
||||
/**
|
||||
* Do not instantiate this class.
|
||||
*/
|
||||
private ExifInterfaceCompat() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link ExifInterface}.
|
||||
* Original constructor won't check filename value, so if null value has been passed,
|
||||
* the process will be killed because of SIGSEGV.
|
||||
* Google Play crash report system cannot perceive this crash, so this method will throw
|
||||
* {@link NullPointerException} when the filename is null.
|
||||
*
|
||||
* @param filename a JPEG filename.
|
||||
* @return {@link ExifInterface} instance.
|
||||
* @throws IOException something wrong with I/O.
|
||||
*/
|
||||
public static ExifInterface newInstance(String filename) throws IOException {
|
||||
if (filename == null) throw new NullPointerException("filename should not be null");
|
||||
return new ExifInterface(filename);
|
||||
}
|
||||
|
||||
private static Date getExifDateTime(String filepath) {
|
||||
ExifInterface exif;
|
||||
try {
|
||||
// ExifInterface does not check whether file path is null or not,
|
||||
// so passing null file path argument to its constructor causing SIGSEGV.
|
||||
// We should avoid such a situation by checking file path string.
|
||||
exif = newInstance(filepath);
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "cannot read exif", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
String date = exif.getAttribute(ExifInterface.TAG_DATETIME);
|
||||
if (TextUtils.isEmpty(date)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
|
||||
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return formatter.parse(date);
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "failed to parse date taken", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read exif info and get datetime value of the photo.
|
||||
*
|
||||
* @param filepath to get datetime
|
||||
* @return when a photo taken.
|
||||
*/
|
||||
public static long getExifDateTimeInMillis(String filepath) {
|
||||
Date datetime = getExifDateTime(filepath);
|
||||
if (datetime == null) {
|
||||
return -1;
|
||||
}
|
||||
return datetime.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read exif info and get orientation value of the photo.
|
||||
*
|
||||
* @param filepath to get exif.
|
||||
* @return exif orientation value
|
||||
*/
|
||||
public static int getExifOrientation(String filepath) {
|
||||
ExifInterface exif;
|
||||
try {
|
||||
// ExifInterface does not check whether file path is null or not,
|
||||
// so passing null file path argument to its constructor causing SIGSEGV.
|
||||
// We should avoid such a situation by checking file path string.
|
||||
exif = newInstance(filepath);
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "cannot read exif", ex);
|
||||
return EXIF_DEGREE_FALLBACK_VALUE;
|
||||
}
|
||||
|
||||
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, EXIF_DEGREE_FALLBACK_VALUE);
|
||||
if (orientation == EXIF_DEGREE_FALLBACK_VALUE) {
|
||||
return 0;
|
||||
}
|
||||
// We only recognize a subset of orientation tag values.
|
||||
switch (orientation) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return 90;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return 180;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return 270;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.core.os.EnvironmentCompat;
|
||||
|
||||
import com.zhihu.matisse.internal.entity.CaptureStrategy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class MediaStoreCompat {
|
||||
|
||||
private final WeakReference<Activity> mContext;
|
||||
private final WeakReference<Fragment> mFragment;
|
||||
private CaptureStrategy mCaptureStrategy;
|
||||
private Uri mCurrentPhotoUri;
|
||||
private String mCurrentPhotoPath;
|
||||
|
||||
public MediaStoreCompat(Activity activity) {
|
||||
mContext = new WeakReference<>(activity);
|
||||
mFragment = null;
|
||||
}
|
||||
|
||||
public MediaStoreCompat(Activity activity, Fragment fragment) {
|
||||
mContext = new WeakReference<>(activity);
|
||||
mFragment = new WeakReference<>(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the device has a camera feature or not.
|
||||
*
|
||||
* @param context a context to check for camera feature.
|
||||
* @return true if the device has a camera feature. false otherwise.
|
||||
*/
|
||||
public static boolean hasCameraFeature(Context context) {
|
||||
PackageManager pm = context.getApplicationContext().getPackageManager();
|
||||
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
||||
}
|
||||
|
||||
public void setCaptureStrategy(CaptureStrategy strategy) {
|
||||
mCaptureStrategy = strategy;
|
||||
}
|
||||
|
||||
public void dispatchCaptureIntent(Context context, int requestCode) {
|
||||
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (captureIntent.resolveActivity(context.getPackageManager()) != null) {
|
||||
File photoFile = null;
|
||||
try {
|
||||
photoFile = createImageFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (photoFile != null) {
|
||||
mCurrentPhotoPath = photoFile.getAbsolutePath();
|
||||
mCurrentPhotoUri = FileProvider.getUriForFile(mContext.get(),
|
||||
mCaptureStrategy.authority, photoFile);
|
||||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCurrentPhotoUri);
|
||||
captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
List<ResolveInfo> resInfoList = context.getPackageManager()
|
||||
.queryIntentActivities(captureIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
context.grantUriPermission(packageName, mCurrentPhotoUri,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
if (mFragment != null) {
|
||||
mFragment.get().startActivityForResult(captureIntent, requestCode);
|
||||
} else {
|
||||
mContext.get().startActivityForResult(captureIntent, requestCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private File createImageFile() throws IOException {
|
||||
// Create an image file name
|
||||
String timeStamp =
|
||||
new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
||||
String imageFileName = String.format("JPEG_%s.jpg", timeStamp);
|
||||
File storageDir;
|
||||
if (mCaptureStrategy.isPublic) {
|
||||
storageDir = Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES);
|
||||
if (!storageDir.exists()) storageDir.mkdirs();
|
||||
} else {
|
||||
storageDir = mContext.get().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||
}
|
||||
if (mCaptureStrategy.directory != null) {
|
||||
storageDir = new File(storageDir, mCaptureStrategy.directory);
|
||||
if (!storageDir.exists()) storageDir.mkdirs();
|
||||
}
|
||||
|
||||
// Avoid joining path components manually
|
||||
File tempFile = new File(storageDir, imageFileName);
|
||||
|
||||
// Handle the situation that user's external storage is not ready
|
||||
if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
public Uri getCurrentPhotoUri() {
|
||||
return mCurrentPhotoUri;
|
||||
}
|
||||
|
||||
public String getCurrentPhotoPath() {
|
||||
return mCurrentPhotoPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.zhihu.matisse.internal.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/a/27271131/4739220
|
||||
*/
|
||||
|
||||
public class PathUtils {
|
||||
/**
|
||||
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||
* Framework Documents, as well as the _data field for the MediaStore and
|
||||
* other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @author paulburke
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public static String getPath(final Context context, final Uri uri) {
|
||||
// DocumentProvider
|
||||
if (Platform.hasKitKat() && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
|
||||
// TODO handle non-primary volumes
|
||||
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
} else if (isMediaDocument(uri)) { // MediaProvider
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[]{
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
} else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general)
|
||||
return getDataColumn(context, uri, null, null);
|
||||
} else if ("file".equalsIgnoreCase(uri.getScheme())) { // File
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the data column for this Uri. This is useful for
|
||||
* MediaStore Uris, and other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param selection (Optional) Filter used in the query.
|
||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||
* @return The value of the _data column, which is typically a file path.
|
||||
*/
|
||||
public static String getDataColumn(Context context, Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = "_data";
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int columnIndex = cursor.getColumnIndexOrThrow(column);
|
||||
return cursor.getString(columnIndex);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
*/
|
||||
public static boolean isExternalStorageDocument(Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is DownloadsProvider.
|
||||
*/
|
||||
public static boolean isDownloadsDocument(Uri uri) {
|
||||
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is MediaProvider.
|
||||
*/
|
||||
public static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Point;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import com.zhihu.matisse.MimeType;
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.filter.Filter;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.entity.IncapableCause;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class PhotoMetadataUtils {
|
||||
private static final String TAG = PhotoMetadataUtils.class.getSimpleName();
|
||||
private static final int MAX_WIDTH = 1600;
|
||||
private static final String SCHEME_CONTENT = "content";
|
||||
|
||||
private PhotoMetadataUtils() {
|
||||
throw new AssertionError("oops! the utility class is about to be instantiated...");
|
||||
}
|
||||
|
||||
public static int getPixelsCount(ContentResolver resolver, Uri uri) {
|
||||
Point size = getBitmapBound(resolver, uri);
|
||||
return size.x * size.y;
|
||||
}
|
||||
|
||||
public static Point getBitmapSize(Uri uri, Activity activity) {
|
||||
ContentResolver resolver = activity.getContentResolver();
|
||||
Point imageSize = getBitmapBound(resolver, uri);
|
||||
int w = imageSize.x;
|
||||
int h = imageSize.y;
|
||||
if (PhotoMetadataUtils.shouldRotate(resolver, uri)) {
|
||||
w = imageSize.y;
|
||||
h = imageSize.x;
|
||||
}
|
||||
if (h == 0) return new Point(MAX_WIDTH, MAX_WIDTH);
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
float screenWidth = (float) metrics.widthPixels;
|
||||
float screenHeight = (float) metrics.heightPixels;
|
||||
float widthScale = screenWidth / w;
|
||||
float heightScale = screenHeight / h;
|
||||
if (widthScale > heightScale) {
|
||||
return new Point((int) (w * widthScale), (int) (h * heightScale));
|
||||
}
|
||||
return new Point((int) (w * widthScale), (int) (h * heightScale));
|
||||
}
|
||||
|
||||
public static Point getBitmapBound(ContentResolver resolver, Uri uri) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
is = resolver.openInputStream(uri);
|
||||
BitmapFactory.decodeStream(is, null, options);
|
||||
int width = options.outWidth;
|
||||
int height = options.outHeight;
|
||||
return new Point(width, height);
|
||||
} catch (FileNotFoundException e) {
|
||||
return new Point(0, 0);
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPath(ContentResolver resolver, Uri uri) {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SCHEME_CONTENT.equals(uri.getScheme())) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = resolver.query(uri, new String[]{MediaStore.Images.ImageColumns.DATA},
|
||||
null, null, null);
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
return null;
|
||||
}
|
||||
return cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
public static IncapableCause isAcceptable(Context context, Item item) {
|
||||
if (!isSelectableType(context, item)) {
|
||||
return new IncapableCause(context.getString(R.string.error_file_type));
|
||||
}
|
||||
|
||||
if (SelectionSpec.getInstance().filters != null) {
|
||||
for (Filter filter : SelectionSpec.getInstance().filters) {
|
||||
IncapableCause incapableCause = filter.filter(context, item);
|
||||
if (incapableCause != null) {
|
||||
return incapableCause;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSelectableType(Context context, Item item) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
for (MimeType type : SelectionSpec.getInstance().mimeTypeSet) {
|
||||
if (type.checkType(resolver, item.getContentUri())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean shouldRotate(ContentResolver resolver, Uri uri) {
|
||||
ExifInterface exif;
|
||||
try {
|
||||
exif = ExifInterfaceCompat.newInstance(getPath(resolver, uri));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "could not read exif info of the image: " + uri);
|
||||
return false;
|
||||
}
|
||||
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
|
||||
return orientation == ExifInterface.ORIENTATION_ROTATE_90
|
||||
|| orientation == ExifInterface.ORIENTATION_ROTATE_270;
|
||||
}
|
||||
|
||||
public static float getSizeInMB(long sizeInBytes) {
|
||||
DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
|
||||
df.applyPattern("0.0");
|
||||
String result = df.format((float) sizeInBytes / 1024 / 1024);
|
||||
Log.e(TAG, "getSizeInMB: " + result);
|
||||
result = result.replaceAll(",", "."); // in some case , 0.0 will be 0,0
|
||||
return Float.valueOf(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zhihu.matisse.internal.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* @author JoongWon Baik
|
||||
*/
|
||||
public class Platform {
|
||||
public static boolean hasICS() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
}
|
||||
|
||||
public static boolean hasKitKat() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.zhihu.matisse.internal.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* @author 工藤
|
||||
* @email gougou@16fan.com
|
||||
* create at 2018年10月23日12:17:59
|
||||
* description:媒体扫描
|
||||
*/
|
||||
public class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient {
|
||||
|
||||
private MediaScannerConnection mMsc;
|
||||
private String mPath;
|
||||
private ScanListener mListener;
|
||||
|
||||
public interface ScanListener {
|
||||
|
||||
/**
|
||||
* scan finish
|
||||
*/
|
||||
void onScanFinish();
|
||||
}
|
||||
|
||||
public SingleMediaScanner(Context context, String mPath, ScanListener mListener) {
|
||||
this.mPath = mPath;
|
||||
this.mListener = mListener;
|
||||
this.mMsc = new MediaScannerConnection(context, this);
|
||||
this.mMsc.connect();
|
||||
}
|
||||
|
||||
@Override public void onMediaScannerConnected() {
|
||||
mMsc.scanFile(mPath, null);
|
||||
}
|
||||
|
||||
@Override public void onScanCompleted(String mPath, Uri mUri) {
|
||||
mMsc.disconnect();
|
||||
if (mListener != null) {
|
||||
mListener.onScanFinish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.internal.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class UIUtils {
|
||||
|
||||
public static int spanCount(Context context, int gridExpectedSize) {
|
||||
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
float expected = (float) screenWidth / (float) gridExpectedSize;
|
||||
int spanCount = Math.round(expected);
|
||||
if (spanCount == 0) {
|
||||
spanCount = 1;
|
||||
}
|
||||
return spanCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.zhihu.matisse.listener;
|
||||
|
||||
|
||||
/**
|
||||
* when original is enabled , callback immediately when user check or uncheck original.
|
||||
*/
|
||||
public interface OnCheckedListener {
|
||||
void onCheck(boolean isChecked);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.zhihu.matisse.listener;
|
||||
|
||||
/**
|
||||
* PreViewItemFragment 和 BasePreViewActivity 通信的接口 ,为了方便拿到 ImageViewTouch 的点击事件
|
||||
*/
|
||||
public interface OnFragmentInteractionListener {
|
||||
/**
|
||||
* ImageViewTouch 被点击了
|
||||
*/
|
||||
void onClick();
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.listener;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OnSelectedListener {
|
||||
/**
|
||||
* @param uriList the selected item {@link Uri} list.
|
||||
* @param pathList the selected item file path list.
|
||||
*/
|
||||
void onSelected(@NonNull List<Uri> uriList, @NonNull List<String> pathList);
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2017 Zhihu Inc.
|
||||
*
|
||||
* 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.zhihu.matisse.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zhihu.matisse.R;
|
||||
import com.zhihu.matisse.internal.entity.Album;
|
||||
import com.zhihu.matisse.internal.entity.Item;
|
||||
import com.zhihu.matisse.internal.entity.SelectionSpec;
|
||||
import com.zhihu.matisse.internal.model.AlbumCollection;
|
||||
import com.zhihu.matisse.internal.model.SelectedItemCollection;
|
||||
import com.zhihu.matisse.internal.ui.AlbumPreviewActivity;
|
||||
import com.zhihu.matisse.internal.ui.BasePreviewActivity;
|
||||
import com.zhihu.matisse.internal.ui.MediaSelectionFragment;
|
||||
import com.zhihu.matisse.internal.ui.SelectedPreviewActivity;
|
||||
import com.zhihu.matisse.internal.ui.adapter.AlbumMediaAdapter;
|
||||
import com.zhihu.matisse.internal.ui.adapter.AlbumsAdapter;
|
||||
import com.zhihu.matisse.internal.ui.widget.AlbumsSpinner;
|
||||
import com.zhihu.matisse.internal.ui.widget.CheckRadioView;
|
||||
import com.zhihu.matisse.internal.ui.widget.IncapableDialog;
|
||||
import com.zhihu.matisse.internal.utils.MediaStoreCompat;
|
||||
import com.zhihu.matisse.internal.utils.PathUtils;
|
||||
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
|
||||
|
||||
import com.zhihu.matisse.internal.utils.SingleMediaScanner;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Main Activity to display albums and media content (images/videos) in each album
|
||||
* and also support media selecting operations.
|
||||
*/
|
||||
public class MatisseActivity extends AppCompatActivity implements
|
||||
AlbumCollection.AlbumCallbacks, AdapterView.OnItemSelectedListener,
|
||||
MediaSelectionFragment.SelectionProvider, View.OnClickListener,
|
||||
AlbumMediaAdapter.CheckStateListener, AlbumMediaAdapter.OnMediaClickListener,
|
||||
AlbumMediaAdapter.OnPhotoCapture {
|
||||
|
||||
public static final String EXTRA_RESULT_SELECTION = "extra_result_selection";
|
||||
public static final String EXTRA_RESULT_SELECTION_PATH = "extra_result_selection_path";
|
||||
public static final String EXTRA_RESULT_ORIGINAL_ENABLE = "extra_result_original_enable";
|
||||
private static final int REQUEST_CODE_PREVIEW = 23;
|
||||
private static final int REQUEST_CODE_CAPTURE = 24;
|
||||
public static final String CHECK_STATE = "checkState";
|
||||
private final AlbumCollection mAlbumCollection = new AlbumCollection();
|
||||
private MediaStoreCompat mMediaStoreCompat;
|
||||
private SelectedItemCollection mSelectedCollection = new SelectedItemCollection(this);
|
||||
private SelectionSpec mSpec;
|
||||
|
||||
private AlbumsSpinner mAlbumsSpinner;
|
||||
private AlbumsAdapter mAlbumsAdapter;
|
||||
private TextView mButtonPreview;
|
||||
private TextView mButtonApply;
|
||||
private View mContainer;
|
||||
private View mEmptyView;
|
||||
|
||||
private LinearLayout mOriginalLayout;
|
||||
private CheckRadioView mOriginal;
|
||||
private boolean mOriginalEnable;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
// programmatically set theme before super.onCreate()
|
||||
mSpec = SelectionSpec.getInstance();
|
||||
setTheme(mSpec.themeId);
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!mSpec.hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
setContentView(R.layout.activity_matisse);
|
||||
|
||||
if (mSpec.needOrientationRestriction()) {
|
||||
setRequestedOrientation(mSpec.orientation);
|
||||
}
|
||||
|
||||
if (mSpec.capture) {
|
||||
mMediaStoreCompat = new MediaStoreCompat(this);
|
||||
if (mSpec.captureStrategy == null)
|
||||
throw new RuntimeException("Don't forget to set CaptureStrategy.");
|
||||
mMediaStoreCompat.setCaptureStrategy(mSpec.captureStrategy);
|
||||
}
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(false);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
Drawable navigationIcon = toolbar.getNavigationIcon();
|
||||
TypedArray ta = getTheme().obtainStyledAttributes(new int[]{R.attr.album_element_color});
|
||||
int color = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
navigationIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
mButtonPreview = (TextView) findViewById(R.id.button_preview);
|
||||
mButtonApply = (TextView) findViewById(R.id.button_apply);
|
||||
mButtonPreview.setOnClickListener(this);
|
||||
mButtonApply.setOnClickListener(this);
|
||||
mContainer = findViewById(R.id.container);
|
||||
mEmptyView = findViewById(R.id.empty_view);
|
||||
mOriginalLayout = findViewById(R.id.originalLayout);
|
||||
mOriginal = findViewById(R.id.original);
|
||||
mOriginalLayout.setOnClickListener(this);
|
||||
|
||||
mSelectedCollection.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
mOriginalEnable = savedInstanceState.getBoolean(CHECK_STATE);
|
||||
}
|
||||
updateBottomToolbar();
|
||||
|
||||
mAlbumsAdapter = new AlbumsAdapter(this, null, false);
|
||||
mAlbumsSpinner = new AlbumsSpinner(this);
|
||||
mAlbumsSpinner.setOnItemSelectedListener(this);
|
||||
mAlbumsSpinner.setSelectedTextView((TextView) findViewById(R.id.selected_album));
|
||||
mAlbumsSpinner.setPopupAnchorView(findViewById(R.id.toolbar));
|
||||
mAlbumsSpinner.setAdapter(mAlbumsAdapter);
|
||||
mAlbumCollection.onCreate(this, this);
|
||||
mAlbumCollection.onRestoreInstanceState(savedInstanceState);
|
||||
mAlbumCollection.loadAlbums();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mSelectedCollection.onSaveInstanceState(outState);
|
||||
mAlbumCollection.onSaveInstanceState(outState);
|
||||
outState.putBoolean("checkState", mOriginalEnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mAlbumCollection.onDestroy();
|
||||
mSpec.onCheckedListener = null;
|
||||
mSpec.onSelectedListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode != RESULT_OK)
|
||||
return;
|
||||
|
||||
if (requestCode == REQUEST_CODE_PREVIEW) {
|
||||
Bundle resultBundle = data.getBundleExtra(BasePreviewActivity.EXTRA_RESULT_BUNDLE);
|
||||
ArrayList<Item> selected = resultBundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION);
|
||||
mOriginalEnable = data.getBooleanExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, false);
|
||||
int collectionType = resultBundle.getInt(SelectedItemCollection.STATE_COLLECTION_TYPE,
|
||||
SelectedItemCollection.COLLECTION_UNDEFINED);
|
||||
if (data.getBooleanExtra(BasePreviewActivity.EXTRA_RESULT_APPLY, false)) {
|
||||
Intent result = new Intent();
|
||||
ArrayList<Uri> selectedUris = new ArrayList<>();
|
||||
ArrayList<String> selectedPaths = new ArrayList<>();
|
||||
if (selected != null) {
|
||||
for (Item item : selected) {
|
||||
selectedUris.add(item.getContentUri());
|
||||
selectedPaths.add(PathUtils.getPath(this, item.getContentUri()));
|
||||
}
|
||||
}
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selectedUris);
|
||||
result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPaths);
|
||||
result.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
setResult(RESULT_OK, result);
|
||||
finish();
|
||||
} else {
|
||||
mSelectedCollection.overwrite(selected, collectionType);
|
||||
Fragment mediaSelectionFragment = getSupportFragmentManager().findFragmentByTag(
|
||||
MediaSelectionFragment.class.getSimpleName());
|
||||
if (mediaSelectionFragment instanceof MediaSelectionFragment) {
|
||||
((MediaSelectionFragment) mediaSelectionFragment).refreshMediaGrid();
|
||||
}
|
||||
updateBottomToolbar();
|
||||
}
|
||||
} else if (requestCode == REQUEST_CODE_CAPTURE) {
|
||||
// Just pass the data back to previous calling Activity.
|
||||
Uri contentUri = mMediaStoreCompat.getCurrentPhotoUri();
|
||||
String path = mMediaStoreCompat.getCurrentPhotoPath();
|
||||
ArrayList<Uri> selected = new ArrayList<>();
|
||||
selected.add(contentUri);
|
||||
ArrayList<String> selectedPath = new ArrayList<>();
|
||||
selectedPath.add(path);
|
||||
Intent result = new Intent();
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selected);
|
||||
result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPath);
|
||||
setResult(RESULT_OK, result);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
MatisseActivity.this.revokeUriPermission(contentUri,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
new SingleMediaScanner(this.getApplicationContext(), path, new SingleMediaScanner.ScanListener() {
|
||||
@Override public void onScanFinish() {
|
||||
Log.i("SingleMediaScanner", "scan finish!");
|
||||
}
|
||||
});
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBottomToolbar() {
|
||||
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
if (selectedCount == 0) {
|
||||
mButtonPreview.setEnabled(false);
|
||||
mButtonApply.setEnabled(false);
|
||||
mButtonApply.setText(getString(R.string.button_apply_default));
|
||||
} else if (selectedCount == 1 && mSpec.singleSelectionModeEnabled()) {
|
||||
mButtonPreview.setEnabled(true);
|
||||
mButtonApply.setText(R.string.button_apply_default);
|
||||
mButtonApply.setEnabled(true);
|
||||
} else {
|
||||
mButtonPreview.setEnabled(true);
|
||||
mButtonApply.setEnabled(true);
|
||||
mButtonApply.setText(getString(R.string.button_apply, selectedCount));
|
||||
}
|
||||
|
||||
|
||||
if (mSpec.originalable) {
|
||||
mOriginalLayout.setVisibility(View.VISIBLE);
|
||||
updateOriginalState();
|
||||
} else {
|
||||
mOriginalLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void updateOriginalState() {
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
if (countOverMaxSize() > 0) {
|
||||
|
||||
if (mOriginalEnable) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_size, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
|
||||
mOriginal.setChecked(false);
|
||||
mOriginalEnable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int countOverMaxSize() {
|
||||
int count = 0;
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
for (int i = 0; i < selectedCount; i++) {
|
||||
Item item = mSelectedCollection.asList().get(i);
|
||||
|
||||
if (item.isImage()) {
|
||||
float size = PhotoMetadataUtils.getSizeInMB(item.size);
|
||||
if (size > mSpec.originalMaxSize) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.button_preview) {
|
||||
Intent intent = new Intent(this, SelectedPreviewActivity.class);
|
||||
intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle());
|
||||
intent.putExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
startActivityForResult(intent, REQUEST_CODE_PREVIEW);
|
||||
} else if (v.getId() == R.id.button_apply) {
|
||||
Intent result = new Intent();
|
||||
ArrayList<Uri> selectedUris = (ArrayList<Uri>) mSelectedCollection.asListOfUri();
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selectedUris);
|
||||
ArrayList<String> selectedPaths = (ArrayList<String>) mSelectedCollection.asListOfString();
|
||||
result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPaths);
|
||||
result.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
setResult(RESULT_OK, result);
|
||||
finish();
|
||||
} else if (v.getId() == R.id.originalLayout) {
|
||||
int count = countOverMaxSize();
|
||||
if (count > 0) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_count, count, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
mOriginalEnable = !mOriginalEnable;
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
|
||||
if (mSpec.onCheckedListener != null) {
|
||||
mSpec.onCheckedListener.onCheck(mOriginalEnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAlbumCollection.setStateCurrentSelection(position);
|
||||
mAlbumsAdapter.getCursor().moveToPosition(position);
|
||||
Album album = Album.valueOf(mAlbumsAdapter.getCursor());
|
||||
if (album.isAll() && SelectionSpec.getInstance().capture) {
|
||||
album.addCaptureCount();
|
||||
}
|
||||
onAlbumSelected(album);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumLoad(final Cursor cursor) {
|
||||
mAlbumsAdapter.swapCursor(cursor);
|
||||
// select default album.
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
cursor.moveToPosition(mAlbumCollection.getCurrentSelection());
|
||||
mAlbumsSpinner.setSelection(MatisseActivity.this,
|
||||
mAlbumCollection.getCurrentSelection());
|
||||
Album album = Album.valueOf(cursor);
|
||||
if (album.isAll() && SelectionSpec.getInstance().capture) {
|
||||
album.addCaptureCount();
|
||||
}
|
||||
onAlbumSelected(album);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumReset() {
|
||||
mAlbumsAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
private void onAlbumSelected(Album album) {
|
||||
if (album.isAll() && album.isEmpty()) {
|
||||
mContainer.setVisibility(View.GONE);
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mContainer.setVisibility(View.VISIBLE);
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
Fragment fragment = MediaSelectionFragment.newInstance(album);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, fragment, MediaSelectionFragment.class.getSimpleName())
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
// notify bottom toolbar that check state changed.
|
||||
updateBottomToolbar();
|
||||
|
||||
if (mSpec.onSelectedListener != null) {
|
||||
mSpec.onSelectedListener.onSelected(
|
||||
mSelectedCollection.asListOfUri(), mSelectedCollection.asListOfString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Album album, Item item, int adapterPosition) {
|
||||
Intent intent = new Intent(this, AlbumPreviewActivity.class);
|
||||
intent.putExtra(AlbumPreviewActivity.EXTRA_ALBUM, album);
|
||||
intent.putExtra(AlbumPreviewActivity.EXTRA_ITEM, item);
|
||||
intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle());
|
||||
intent.putExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
startActivityForResult(intent, REQUEST_CODE_PREVIEW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SelectedItemCollection provideSelectedItemCollection() {
|
||||
return mSelectedCollection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void capture() {
|
||||
if (mMediaStoreCompat != null) {
|
||||
mMediaStoreCompat.dispatchCaptureIntent(this, REQUEST_CODE_CAPTURE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2017 Zhihu Inc.
|
||||
|
||||
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:color="@color/dracula_bottom_toolbar_apply_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/dracula_bottom_toolbar_apply_text"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2017 Zhihu Inc.
|
||||
|
||||
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:color="@color/dracula_bottom_toolbar_preview_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/dracula_bottom_toolbar_preview_text"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2017 Zhihu Inc.
|
||||
|
||||
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:color="@color/dracula_preview_bottom_toolbar_apply_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/dracula_preview_bottom_toolbar_apply_text"/>
|
||||
</selector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user