Compare commits

..

11 Commits

Author SHA1 Message Date
90a2f98712 fix:【光环助手】“视频横屏滑动”专题视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-6939 2024-11-15 11:03:34 +08:00
c6c6223697 fix:【光环助手】“视频横屏滑动”专题视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-6939 2024-11-14 17:33:48 +08:00
b931da86c4 fix:【光环助手】“视频横屏滑动”专题视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-6939 2024-11-14 16:15:00 +08:00
5d4cff5d59 fix:【光环助手】“视频横屏滑动”专题视频播放问题(ci) https://jira.shanqu.cc/browse/GHZSCY-6939 2024-11-14 11:10:36 +08:00
6a157d1d7e fix:【光环助手】“视频横屏滑动”专题视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-6939 2024-11-14 11:09:02 +08:00
9b1598b719 fix: 样式组件优化汇总-2024/11/12-测试组-客户端 https://jira.shanqu.cc/browse/GHZSCY-6981 2024-11-13 16:34:25 +08:00
287a725e38 feat: 样式组件优化汇总—客户端(ci) https://jira.shanqu.cc/browse/GHZSCY-6866 2024-11-11 16:03:27 +08:00
460f20cb2d feat: 样式组件优化汇总—客户端 https://jira.shanqu.cc/browse/GHZSCY-6866 2024-11-11 16:03:27 +08:00
0f9f0b7c9a Merge branch 'feat/GHZSCY-6645' into 'dev'
feat:媒体文件上传控件优化 (二、三)—客户端 https://jira.shanqu.cc/browse/GHZSCY-6645

See merge request halo/android/assistant-android!1967
2024-11-06 15:09:48 +08:00
c29efbefd7 feat:媒体文件上传控件优化 (二、三)—客户端 https://jira.shanqu.cc/browse/GHZSCY-6645 2024-11-06 15:09:47 +08:00
56151bc38d Merge branch 'fix/va-test-install' into 'dev'
fix: 1. 测试版本没有 从SD卡安装 的问题。2. 重复卸载安装不生效的bug

See merge request halo/android/assistant-android!1960
2024-10-31 16:07:30 +08:00
65 changed files with 1876 additions and 744 deletions

View File

@ -72,6 +72,8 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-6866
- fix/GHZSCY-6939
# 代码检查
sonarqube_analysis:
@ -157,3 +159,5 @@ oss-upload&send-email:
only:
- dev
- release
- feat/GHZSCY-6866
- fix/GHZSCY-6939

View File

@ -517,10 +517,6 @@
android:name=".video.data.VideoDataActivity"
android:screenOrientation="portrait" />
<activity
android:name=".video.poster.PosterEditActivity"
android:screenOrientation="portrait" />
<activity
android:name=".forum.detail.ForumDetailActivity"
android:screenOrientation="portrait" />
@ -809,6 +805,10 @@
android:screenOrientation="portrait"
android:theme="@style/AppCompatTheme.APP" />
<activity
android:name=".video.poster.PosterEditActivity"
android:screenOrientation="portrait" />
<!-- <activity-->
<!-- android:name="${applicationId}.douyinapi.DouYinEntryActivity"-->
<!-- android:launchMode="singleTask"-->

View File

@ -21,26 +21,31 @@ import com.gh.common.view.RichEditor
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.qa.editor.*
import com.gh.gamecenter.entity.GamesCollectionEntity
import com.gh.gamecenter.entity.MyVideoEntity
import com.gh.gamecenter.entity.VideoEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.qa.editor.*
import com.gh.gamecenter.qa.entity.EditorInsertEntity
import com.gh.gamecenter.video.poster.PosterEditActivity
import com.gh.gamecenter.video.poster.video.VideoPosterFragment
import com.gh.gamecenter.video.upload.UploadManager
import com.google.gson.JsonObject
import com.halo.assistant.HaloApp
import com.lightgame.utils.Util_System_Keyboard
import com.lightgame.utils.Utils
import com.lightgame.view.CheckableImageView
import com.therouter.TheRouter
import io.reactivex.disposables.Disposable
import org.json.JSONArray
import org.json.JSONObject
@ -229,6 +234,7 @@ abstract class BaseRichEditorActivity<VM : BaseRichEditorViewModel> constructor(
@SuppressLint("AddJavascriptInterface", "ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
VideoPosterFragment.createVideoCoverFile(this)
findView()
onRichClick()
mViewModel = provideViewModel()
@ -739,9 +745,9 @@ abstract class BaseRichEditorActivity<VM : BaseRichEditorViewModel> constructor(
mViewModel.id = id
mViewModel.videoId = videoId
val videoEntity = VideoEntity(url = url)
val intent =
PosterEditActivity.getIntentByVideo(this@BaseRichEditorActivity, videoEntity)
startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
TheRouter.build(RouteConsts.activity.videoCoverEditActivity)
.withParcelable(EntranceConsts.KEY_VIDEO_ENTITY, videoEntity)
.navigation(this@BaseRichEditorActivity, REQUEST_CODE_IMAGE_CROP)
}
@JavascriptInterface

View File

@ -1,12 +1,9 @@
package com.gh.common.iinterface
interface ISmartRefreshContent {
/**
* 启用/关闭 页面滑动
* @param isScrollEnabled 是否启用
*/
fun setScrollEnabled(isScrollEnabled: Boolean)
import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.scwang.smartrefresh.layout.constant.RefreshState
interface ISmartRefreshContent {
fun onRefresh()
/**
@ -14,4 +11,6 @@ interface ISmartRefreshContent {
* @param isSwipeRefreshEnabled 是否启用
*/
fun setSwipeRefreshEnabled(isSwipeRefreshEnabled: Boolean)
fun onStateChanged(refreshLayout: RefreshLayout, oldState: RefreshState, newState: RefreshState)
}

View File

@ -0,0 +1,58 @@
package com.gh.common.provider
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.core.provider.ICropImageProvider
import com.gh.gamecenter.personalhome.background.BackgroundClipActivity
import com.halo.assistant.fragment.user.UserPortraitCropImageActivity
import com.lightgame.utils.Utils
import com.zhihu.matisse.internal.utils.PathUtils
@com.therouter.inject.ServiceProvider
class CropImageProviderImpl : ICropImageProvider {
override fun getCropImageIntent(data: List<Uri>, imageType: Int, entrance: String, context: Context): Intent? {
if (data.isEmpty()) {
return null
}
val picturePath = PathUtils.getPath(context, data[0])
Utils.log("picturePath = $picturePath")
return when (imageType) {
IMAGE_TYPE_AVATAR -> {// 上传头像
UserPortraitCropImageActivity.getIntent(
context,
picturePath,
"我的光环(选择头像)"
)
}
IMAGE_TYPE_GAME_COLLECTION_COVER -> {// 游戏单封面
CropImageActivity.getIntent(
context,
picturePath,
142 / 328F,
false,
R.layout.layout_game_collection_crop_image_assist,
entrance
)
}
IMAGE_TYPE_PERSONAL_BACKGROUND -> { // 用户主页背景
BackgroundClipActivity.getIntent(context, picturePath, entrance)
}
else ->
null
}
}
companion object {
const val IMAGE_TYPE_AVATAR = 1
const val IMAGE_TYPE_GAME_COLLECTION_COVER = 2
const val IMAGE_TYPE_PERSONAL_BACKGROUND = 3
}
}

View File

@ -3,26 +3,25 @@ package com.gh.gamecenter;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import com.gh.gamecenter.common.base.activity.ToolBarActivity;
import com.gh.gamecenter.common.utils.BitmapUtils;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.common.constant.EntranceConsts;
import com.gh.gamecenter.common.utils.BitmapUtils;
import com.gh.gamecenter.common.view.CropImageCustom;
import com.gh.gamecenter.common.view.cropbox.CropBoxStyle;
import com.gh.gamecenter.core.utils.DisplayUtils;
import java.io.File;
import java.lang.ref.SoftReference;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
/**
* 裁剪图片
*/
@ -32,6 +31,8 @@ public class CropImageActivity extends ToolBarActivity {
public static final String RESULT_CLIP_PATH = "result_clip_path";
public static final String RESULT_ORIGINAL_PATH = "result_original_path";
private SoftReference<Bitmap> reference;
protected boolean mBlackTheme = false;
@ -51,7 +52,7 @@ public class CropImageActivity extends ToolBarActivity {
Intent intent = new Intent(context, CropImageActivity.class);
intent.putExtra(EntranceConsts.KEY_PATH, picturePath);
intent.putExtra(EntranceConsts.KEY_ENTRANCE, entrance);
intent.putExtra(EntranceConsts.KEY_IMAGE_CROP_RATIO, cropRatio);
intent.putExtra(EntranceConsts.KEY_IMAGE_CROP_STYLE, new CropBoxStyle.Rectangle(cropRatio));
intent.putExtra(EntranceConsts.KEY_BLACK_THEME, isBlackTheme);
intent.putExtra(EntranceConsts.KEY_ASSIST_RES, assistRes);
return intent;
@ -68,20 +69,14 @@ public class CropImageActivity extends ToolBarActivity {
super.onCreate(savedInstanceState);
mCropImageCustom = findViewById(R.id.cropimage_custom);
View statusBarView = findViewById(R.id.status_bar);
TextView tvCancel = findViewById(R.id.tv_cancel);
TextView tvSubmit = findViewById(R.id.tv_submit);
mTitleTv.setTextColor(mBlackTheme ? Color.WHITE : Color.BLACK);
mToolbar.setBackgroundColor(getResources().getColor(mBlackTheme ? com.gh.gamecenter.common.R.color.text_28282E : com.gh.gamecenter.common.R.color.white));
statusBarView.setBackgroundColor(getResources().getColor(mBlackTheme ? com.gh.gamecenter.common.R.color.text_28282E : com.gh.gamecenter.common.R.color.white));
setNavigationTitle(getString(R.string.title_crop_image));
setToolbarMenu(R.menu.menu_positive);
MenuItem menuItem = getMenuItem(R.id.layout_menu_positive);
TextView menuButton = menuItem.getActionView().findViewById(R.id.menu_answer_post);
menuButton.setTextColor(getResources().getColor(com.gh.gamecenter.common.R.color.text_theme));
float ratio = getIntent().getFloatExtra(EntranceConsts.KEY_IMAGE_CROP_RATIO, 1F);
mCropImageCustom.setCropRatio(ratio);
CropBoxStyle boxStyle = getIntent().getParcelableExtra(EntranceConsts.KEY_IMAGE_CROP_STYLE);
if (boxStyle == null) {
boxStyle = new CropBoxStyle.Rectangle(1F);
}
mCropImageCustom.setCropBoxStyle(boxStyle);
int assistRes = getIntent().getIntExtra(EntranceConsts.KEY_ASSIST_RES, -1);
if (assistRes > 0) {
@ -89,8 +84,21 @@ public class CropImageActivity extends ToolBarActivity {
addAssistView(view);
}
DisplayUtils.setLightStatusBar(this, !mBlackTheme);
DisplayUtils.setStatusBarColor(this, com.gh.gamecenter.common.R.color.transparent, !mBlackTheme);
DisplayUtils.setLightStatusBar(this, false);
DisplayUtils.setStatusBarColor(this, com.gh.gamecenter.common.R.color.transparent, false);
tvCancel.setOnClickListener(v -> finish());
tvSubmit.setOnClickListener(v -> saveImage());
}
protected void saveImage() {
Intent data = new Intent();
String clipPath = getCacheDir().getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg";
mCropImageCustom.savePicture(clipPath);
data.putExtra(RESULT_CLIP_PATH, clipPath);
setResult(RESULT_OK, data);
finish();
}
@Override
@ -98,20 +106,6 @@ public class CropImageActivity extends ToolBarActivity {
return mBlackTheme ? com.gh.gamecenter.common.R.drawable.ic_toolbar_back_white : com.gh.gamecenter.common.R.drawable.ic_bar_back;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.layout_menu_positive) {
Intent data = new Intent();
String clipPath = getCacheDir().getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg";
mCropImageCustom.savePicture(clipPath);
data.putExtra(RESULT_CLIP_PATH, clipPath);
setResult(RESULT_OK, data);
finish();
}
return super.onMenuItemClick(item);
}
public void addAssistView(View view) {
mCropImageCustom.addAssistView(view);
}

View File

@ -11,6 +11,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import com.gh.common.provider.CropImageProviderImpl.Companion.IMAGE_TYPE_GAME_COLLECTION_COVER
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.common.util.NewLogUtils
@ -45,7 +46,8 @@ class ChooseGameCollectionCoverTypeDialog : BaseDialogFragment() {
requireContext(),
ChooseType.IMAGE,
1,
"创建游戏单"
"创建游戏单",
IMAGE_TYPE_GAME_COLLECTION_COVER
), REQUEST_CODE_IMAGE
)
}
@ -101,7 +103,8 @@ class ChooseGameCollectionCoverTypeDialog : BaseDialogFragment() {
com.gh.gamecenter.common.R.drawable.bg_choose_option_selector.toDrawable(requireContext())
defaultUploadContainer.background =
com.gh.gamecenter.common.R.drawable.bg_choose_option_selector.toDrawable(requireContext())
cancelBtn.background = com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999.toDrawable(requireContext())
cancelBtn.background =
com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999.toDrawable(requireContext())
recommentTv.background = R.drawable.bg_game_collection_cover_tag.toDrawable(requireContext())
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(requireContext()))
localUploadTitleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))

View File

@ -313,14 +313,14 @@ class GameCollectionEditActivity : ToolBarActivity(), ChooseGamesAdapter.ItemDra
val games = mChooseGamesViewModel.chooseGamesLiveData.value ?: arrayListOf()
if (mIsCreateGameCollection) {
SensorsBridge.trackEvent("GameCollectCreateSuccess", json {
"game_collect_status" to if(mBinding.selfOnlyCb.isChecked) "仅自己可见" else "公开"
"game_collect_status" to if (mBinding.selfOnlyCb.isChecked) "仅自己可见" else "公开"
"game_num" to games.size
"game_collect_title" to mBinding.gameCollectionTitleEt.text.toString().trim()
"game_collect_id" to it.data as String
})
} else if (mViewModel.gameCollectionPatch != null) {
SensorsBridge.trackEvent("GameCollectEditSuccess", json {
"game_collect_status" to if(mBinding.selfOnlyCb.isChecked) "仅自己可见" else "公开"
"game_collect_status" to if (mBinding.selfOnlyCb.isChecked) "仅自己可见" else "公开"
"game_num" to games.size
"game_collect_title" to mBinding.gameCollectionTitleEt.text.toString().trim()
"game_collect_id" to mViewModel.gameCollectionPatch?.id
@ -355,7 +355,7 @@ class GameCollectionEditActivity : ToolBarActivity(), ChooseGamesAdapter.ItemDra
CheckLoginUtils.checkLogin(this, mEntrance) {}
return@observe
}
ErrorHelper.handleError(this, errorMsg, false, "发布游戏单", "社区实名") {
ErrorHelper.handleError(this, errorMsg, false, "发布游戏单", "社区实名") {
if (::mMenuPost.isInitialized) {
onMenuItemClick(mMenuPost)
}
@ -433,18 +433,6 @@ class GameCollectionEditActivity : ToolBarActivity(), ChooseGamesAdapter.ItemDra
super.onActivityResult(requestCode, resultCode, data)
if (data == null || resultCode != Activity.RESULT_OK) return
when (requestCode) {
REQUEST_CODE_IMAGE_CROP -> {
val imagePath = data.getStringExtra(CropImageActivity.RESULT_CLIP_PATH) ?: ""
mViewModel.imageUrl = ""
mViewModel.imagePath = imagePath
if (imagePath.isEmpty()) {
mBinding.uploadPictureTv.text = "点击上传图片"
} else {
mBinding.uploadPictureTv.text = "图片上传中..."
mViewModel.uploadPoster()
}
initPosterUI()
}
REQUEST_CHOOSE_TAG -> {
val tags = data.getParcelableArrayListExtra<TagInfoEntity>(GameCollectionTagSelectFragment.SELECTED_TAG)
?: arrayListOf()
@ -457,20 +445,16 @@ class GameCollectionEditActivity : ToolBarActivity(), ChooseGamesAdapter.ItemDra
fun onActivityDialogResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null) {
if (requestCode == REQUEST_CODE_IMAGE) {
val selectedPaths = Matisse.obtainResult(data)
if (!selectedPaths.isNullOrEmpty()) {
val path = PathUtils.getPath(this, selectedPaths[0])
val intent =
CropImageActivity.getIntent(
this,
path,
142 / 328F,
false,
R.layout.layout_game_collection_crop_image_assist,
mEntrance
)
startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
val imagePath = data.getStringExtra(CropImageActivity.RESULT_CLIP_PATH) ?: ""
mViewModel.imageUrl = ""
mViewModel.imagePath = imagePath
if (imagePath.isEmpty()) {
mBinding.uploadPictureTv.text = "点击上传图片"
} else {
mBinding.uploadPictureTv.text = "图片上传中..."
mViewModel.uploadPoster()
}
initPosterUI()
} else if (requestCode == ChooseGameCollectionDefaultCoverDialog.REQUEST_CODE_DEFAULT_IMAGE) {
val entity = data.getParcelableExtra<GameCollectionCoverEntity>(EntranceConsts.KEY_DATA)
if (entity != null) {
@ -731,9 +715,17 @@ class GameCollectionEditActivity : ToolBarActivity(), ChooseGamesAdapter.ItemDra
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
} else {
DialogHelper.showDialog(this, "温馨提示", "游戏单会在1-2个工作日内审核完成您可以在“我的光环-我的游戏单”查看进度", "继续提交", "取消", {
mViewModel.uploadContent(this, requestMap)
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true))
DialogHelper.showDialog(
this,
"温馨提示",
"游戏单会在1-2个工作日内审核完成您可以在“我的光环-我的游戏单”查看进度",
"继续提交",
"取消",
{
mViewModel.uploadContent(this, requestMap)
},
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
}
}
@ -804,7 +796,8 @@ class GameCollectionEditActivity : ToolBarActivity(), ChooseGamesAdapter.ItemDra
activityDivider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(this@GameCollectionEditActivity))
titleDivider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(this@GameCollectionEditActivity))
introduceDivider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(this@GameCollectionEditActivity))
placeholderView.background = com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_8.toDrawable(this@GameCollectionEditActivity)
placeholderView.background =
com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_8.toDrawable(this@GameCollectionEditActivity)
selfOnlyCb.background =
R.drawable.border_round_stroke_0dot5_eee_999.toDrawable(this@GameCollectionEditActivity)
uploadPictureTv.setTextColor(com.gh.gamecenter.common.R.color.text_instance.toColor(this@GameCollectionEditActivity))
@ -912,7 +905,6 @@ class GameCollectionEditActivity : ToolBarActivity(), ChooseGamesAdapter.ItemDra
companion object {
const val REQUEST_CODE_IMAGE = 100
const val REQUEST_CODE_IMAGE_CROP = 101
const val REQUEST_CHOOSE_GAMES = 102
const val REQUEST_CHOOSE_TAG = 103
const val REQUEST_CHOOSE_ACTIVITY = 105

View File

@ -69,6 +69,9 @@ import com.gh.gamecenter.wrapper.SearchToolbarTabWrapperViewModel
import com.halo.assistant.HaloApp
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.scwang.smartrefresh.layout.constant.RefreshState
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -743,7 +746,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
}
}
override fun setScrollEnabled(isScrollEnabled: Boolean) {
private fun setScrollEnabled(isScrollEnabled: Boolean) {
if (::layoutManager.isInitialized) {
layoutManager.isScrollVerticallyEnabled = isScrollEnabled
}
@ -759,6 +762,32 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
}
}
override fun onStateChanged(refreshLayout: RefreshLayout, oldState: RefreshState, newState: RefreshState) {
if (oldState == RefreshState.None && newState == RefreshState.PullDownToRefresh) {
pauseVideo()
} else if ((oldState == RefreshState.TwoLevelFinish || oldState == RefreshState.RefreshFinish) && newState == RefreshState.None) {
val currentPlayer = scrollCalculatorHelper.currentPlayer
if (currentPlayer == null) {
scrollCalculatorHelper.playIfValid()
} else if (currentPlayer.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
resumeVideo()
}
}
when (newState) {
RefreshState.TwoLevel -> {
setScrollEnabled(false)
}
RefreshState.None -> {
setScrollEnabled(true)
}
else -> {
// do nothing
}
}
}
override fun scrollToTop() {
if (::binding.isInitialized) {
binding.gameList.stopScroll()

View File

@ -363,7 +363,7 @@ class CommonContentHomeSlideWithCardsUi(
homeSlideCardItemBinding.cardIv.visibility = View.VISIBLE
// 防止重复加载图片导致闪烁
if (homeSubSlide.image != cardIv.getTag(R.string.tag_img_url_id)) {
ImageUtils.display(cardIv, homeSubSlide.image, false, AlphaGradientProcess())
ImageUtils.display(cardIv, homeSubSlide.image, true, AlphaGradientProcess())
cardIv.setTag(R.string.tag_img_url_id, homeSubSlide.image)
}
ConstraintSet().apply {
@ -398,7 +398,7 @@ class CommonContentHomeSlideWithCardsUi(
descTv.text = homeSubSlide.cardDesc
// 防止重复加载图片导致闪烁
if (homeSubSlide.image != cardIv.getTag(R.string.tag_img_url_id)) {
ImageUtils.display(cardIv, homeSubSlide.image, false, AlphaGradientProcess())
ImageUtils.display(cardIv, homeSubSlide.image, true, AlphaGradientProcess())
cardIv.setTag(R.string.tag_img_url_id, homeSubSlide.image)
}
ConstraintSet().apply {

View File

@ -109,7 +109,9 @@ class CustomHomeGameItemViewHolder(
item.linkColumn?.id ?: ""
)
}
binding.gameBrief.text = game.recommendText
binding.gameBrief.goneIf(game.recommendText.isEmpty()) {
binding.gameBrief.text = game.recommendText
}
binding.gameImage.visibleIf(game.showImage) {
if (game.isWechatMiniGame()) {
ImageUtils.display(binding.gameImage, game.banner)

View File

@ -29,7 +29,10 @@ class ScrollCalculatorHelper(
fun enableAndPlayIfValid() {
isEnabled = true
playIfValid()
}
fun playIfValid() {
if (mListRv.isAttachedToWindow
&& mListRv.scrollState == RecyclerView.SCROLL_STATE_IDLE) {
playVideo(mListRv)

View File

@ -6,14 +6,16 @@ import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.widget.ImageView
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.common.utils.BitmapUtils
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.common.provider.CropImageProviderImpl.Companion.IMAGE_TYPE_PERSONAL_BACKGROUND
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.BitmapUtils
import com.gh.gamecenter.common.view.cropbox.CropBoxStyle
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.ActivityBackgroundClipBinding
import java.io.File
import java.lang.ref.SoftReference
@ -33,7 +35,6 @@ class BackgroundClipActivity : BaseActivity() {
super.onCreate(savedInstanceState)
DisplayUtils.transparentStatusBar(this)
mBinding = ActivityBackgroundClipBinding.bind(mContentView)
mBinding.cropImageIv.setCropRatio(392 / 360F)
mBinding.cancelTv.setOnClickListener {
finish()
@ -84,6 +85,8 @@ class BackgroundClipActivity : BaseActivity() {
val intent = Intent(context, BackgroundClipActivity::class.java)
intent.putExtra(EntranceConsts.KEY_PATH, picturePath)
intent.putExtra(EntranceConsts.KEY_ENTRANCE, entrance)
intent.putExtra(EntranceConsts.KEY_IMAGE_CROP_STYLE, CropBoxStyle.Rectangle(356F / 328))
intent.putExtra(EntranceConsts.KEY_IMAGE_TYPE, IMAGE_TYPE_PERSONAL_BACKGROUND)
return intent
}
}

View File

@ -8,6 +8,7 @@ import android.view.View
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.provider.CropImageProviderImpl.Companion.IMAGE_TYPE_PERSONAL_BACKGROUND
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
@ -78,7 +79,8 @@ class PersonalityBackgroundFragment : ToolbarFragment() {
requireContext(),
ChooseType.IMAGE,
1,
"个性背景"
"个性背景",
IMAGE_TYPE_PERSONAL_BACKGROUND
), MEDIA_STORE_REQUEST
)
}
@ -89,19 +91,15 @@ class PersonalityBackgroundFragment : ToolbarFragment() {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == MEDIA_STORE_REQUEST && resultCode == Activity.RESULT_OK) {
val selectedPaths = Matisse.obtainPathResult(data)
if (!selectedPaths.isNullOrEmpty()) {
val intent = BackgroundClipActivity.getIntent(requireContext(), selectedPaths[0], mEntrance)
startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
}
} else if (requestCode == REQUEST_CODE_IMAGE_CROP && resultCode == Activity.RESULT_OK) {
if (data != null) {
val imagePath = data.getStringExtra(CropImageActivity.RESULT_CLIP_PATH)
val intent = BackgroundPreviewActivity.getIntent(
requireContext(), imagePath
?: "", null
)
requireActivity().startActivityForResult(intent, CHANGE_BACKGROUND_SUCCESS)
if (imagePath != null) {
val intent = BackgroundPreviewActivity.getIntent(
requireContext(), imagePath
?: "", null
)
requireActivity().startActivityForResult(intent, CHANGE_BACKGROUND_SUCCESS)
}
}
} else if (requestCode == CHANGE_BACKGROUND_SUCCESS && resultCode == Activity.RESULT_OK) {
requireActivity().finish()
@ -119,7 +117,6 @@ class PersonalityBackgroundFragment : ToolbarFragment() {
}
companion object {
const val REQUEST_CODE_IMAGE_CROP = 100
const val MEDIA_STORE_REQUEST = 101
const val CHANGE_BACKGROUND_SUCCESS = 102
}

View File

@ -40,6 +40,7 @@ import com.gh.gamecenter.qa.editor.GameActivity
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.questions.edit.TagsSelectFragment
import com.gh.gamecenter.qa.video.publish.VideoPublishFragment
import com.gh.gamecenter.video.poster.video.VideoPosterFragment
import com.lightgame.utils.Util_System_Keyboard
import com.lightgame.utils.Utils
import org.greenrobot.eventbus.EventBus
@ -123,6 +124,7 @@ class ArticleEditActivity : BaseRichEditorActivity<ArticleEditViewModel>(), Keyb
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
VideoPosterFragment.clearVideoCoverCache(this)
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
mBinding = ActivityCommunityArticleEditBinding.bind(mContentView)
setToolbarMenu(R.menu.menu_answer_post)
@ -651,8 +653,14 @@ class ArticleEditActivity : BaseRichEditorActivity<ArticleEditViewModel>(), Keyb
private fun setForumUI() {
mBinding.forumIconView.visibility = View.VISIBLE
mBinding.forumContainer.background = ContextCompat.getDrawable(this, com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999)
mBinding.articleGameName.setTextColor(ContextCompat.getColor(this, com.gh.gamecenter.common.R.color.text_secondary))
mBinding.forumContainer.background =
ContextCompat.getDrawable(this, com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999)
mBinding.articleGameName.setTextColor(
ContextCompat.getColor(
this,
com.gh.gamecenter.common.R.color.text_secondary
)
)
}
private fun setSectionUI(isEmpty: Boolean) {

View File

@ -50,6 +50,7 @@ import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.draft.QuestionDraftActivity
import com.gh.gamecenter.qa.questions.edit.tip.QuestionTitleTipAdapter
import com.gh.gamecenter.qa.video.publish.VideoPublishFragment
import com.gh.gamecenter.video.poster.video.VideoPosterFragment
import com.lightgame.utils.Util_System_Keyboard
import com.lightgame.utils.Utils
import org.greenrobot.eventbus.EventBus
@ -147,6 +148,7 @@ class QuestionEditActivity : BaseRichEditorActivity<QuestionEditViewModel>(),
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
VideoPosterFragment.clearVideoCoverCache(this)
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
setToolbarMenu(R.menu.menu_question_post)
mToolbar.navigationIcon = null
@ -280,12 +282,14 @@ class QuestionEditActivity : BaseRichEditorActivity<QuestionEditViewModel>(),
detailEntity != null -> { // 问题编辑
setPatchContent(detailEntity)
}
draftEntity != null -> { //草稿编辑
mViewModel.questionDraftEntity = draftEntity
setQuestionDraft(draftEntity)
mViewModel.getQuestionDraftContent(draftEntity.id)
mBaseHandler.sendEmptyMessageDelayed(1, SAVE_DRAFTS_INTERVAL_TIME.toLong())
}
else -> { // 新增问题
var searchKey = intent.getStringExtra(EntranceConsts.KEY_QUESTIONS_SEARCH_KEY)
if (!searchKey.isNullOrEmpty() && searchKey.length > QuestionEditViewModel.QUESTION_TITLE_MAX_LENGTH)
@ -468,6 +472,7 @@ class QuestionEditActivity : BaseRichEditorActivity<QuestionEditViewModel>(),
finish()
}
}
SaveDraftType.AUTO -> {
if (pair.second) {
if (mPostDraftsCount >= SAVE_DRAFTS_TOAST_COUNT) {
@ -478,6 +483,7 @@ class QuestionEditActivity : BaseRichEditorActivity<QuestionEditViewModel>(),
}
}
}
SaveDraftType.SKIP -> {
if (pair.second) {
Utils.toast(this, "问题已保存到草稿箱")
@ -690,7 +696,12 @@ class QuestionEditActivity : BaseRichEditorActivity<QuestionEditViewModel>(),
return false
}
DialogHelper.showDialog(
this, "提示", "确定退出修改?已编辑的内容将丢失", "继续编辑", " 退出", cancelClickCallback = { finish() },
this,
"提示",
"确定退出修改?已编辑的内容将丢失",
"继续编辑",
" 退出",
cancelClickCallback = { finish() },
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
return true
@ -847,7 +858,12 @@ class QuestionEditActivity : BaseRichEditorActivity<QuestionEditViewModel>(),
mBinding.forumIconView.visibility = View.VISIBLE
mBinding.forumContainer.background =
ContextCompat.getDrawable(this, com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999)
mBinding.chooseForumTv.setTextColor(ContextCompat.getColor(this, com.gh.gamecenter.common.R.color.text_secondary))
mBinding.chooseForumTv.setTextColor(
ContextCompat.getColor(
this,
com.gh.gamecenter.common.R.color.text_secondary
)
)
}
private fun setSectionUI(isEmpty: Boolean) {

View File

@ -13,6 +13,7 @@ import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.qa.BbsType
import com.gh.gamecenter.video.poster.video.VideoPosterFragment
/**
* 发视频
@ -22,6 +23,7 @@ class VideoPublishActivity : ToolBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
VideoPosterFragment.clearVideoCoverCache(this)
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
mToolbar.navigationIcon = null
findViewById<View>(R.id.backBtn).setOnClickListener { onBackPressed() }

View File

@ -23,20 +23,24 @@ import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.common.entity.NotificationUgc
import com.gh.gamecenter.common.mvvm.Status
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentVideoPublishBinding
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.entity.ActivityLabelEntity
import com.gh.gamecenter.entity.ForumDetailEntity
import com.gh.gamecenter.entity.VideoDraftEntity
import com.gh.gamecenter.entity.VideoEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.BbsType
import com.gh.gamecenter.qa.dialog.ChooseActivityDialogFragment
@ -44,12 +48,12 @@ import com.gh.gamecenter.qa.dialog.ChooseForumActivity
import com.gh.gamecenter.qa.dialog.ChooseSectionDialogFragment
import com.gh.gamecenter.qa.dialog.InputUrlDialogFragment
import com.gh.gamecenter.qa.editor.GameActivity
import com.gh.gamecenter.video.poster.PosterEditActivity
import com.gh.gamecenter.video.upload.OnUploadListener
import com.gh.gamecenter.video.upload.UploadManager
import com.gh.gamecenter.video.upload.view.VideoFileEntity
import com.gh.gamecenter.video.videomanager.VideoDraftActivity
import com.lightgame.utils.Util_System_Keyboard
import com.therouter.TheRouter
import java.io.File
class VideoPublishFragment : ToolbarFragment(), KeyboardHeightObserver {
@ -661,7 +665,8 @@ class VideoPublishFragment : ToolbarFragment(), KeyboardHeightObserver {
private fun setForumUI() {
mBinding.forumIconView.visibility = View.VISIBLE
mBinding.forumContainer.background = com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999.toDrawable(requireContext())
mBinding.forumContainer.background =
com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999.toDrawable(requireContext())
mBinding.chooseForumTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
}
@ -757,29 +762,24 @@ class VideoPublishFragment : ToolbarFragment(), KeyboardHeightObserver {
private fun startMediaStore() {
try {
PermissionHelper.checkStoragePermissionBeforeAction(requireContext()) {
var intent: Intent? = null
val postcard = TheRouter.build(RouteConsts.activity.videoCoverEditActivity)
when {
mVideoFileEntity?.url?.isNotEmpty() == true -> {
val videoEntity = VideoEntity(
length = mVideoFileEntity?.length ?: 0, url = mVideoFileEntity?.url ?: ""
)
intent = PosterEditActivity.getIntentByVideo(
requireContext(),
videoEntity
)
postcard.withParcelable(EntranceConsts.KEY_VIDEO_ENTITY, videoEntity)
}
mVideoFileEntity?.path?.isNotEmpty() == true -> {
intent = PosterEditActivity.getIntentByPath(
requireContext(), mVideoFileEntity?.url ?: ""
)
postcard.withString(EntranceConsts.KEY_PATH_VIDEO, mVideoFileEntity?.url ?: "")
}
else -> {
throwExceptionInDebug("video not found")
}
}
startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
postcard.navigation(requireActivity(), REQUEST_CODE_IMAGE_CROP)
}
} catch (e: Exception) {
toast(R.string.media_image_hint)
@ -966,9 +966,11 @@ class VideoPublishFragment : ToolbarFragment(), KeyboardHeightObserver {
super.onDarkModeChanged()
setSectionUI(mViewModel.selectSection == null)
mBinding.activityTv.setTextColor(
if (mViewModel.selectActivityLabelEntity != null) com.gh.gamecenter.common.R.color.text_FA8500.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_primary.toColor(
requireContext()
)
if (mViewModel.selectActivityLabelEntity != null) {
com.gh.gamecenter.common.R.color.text_FA8500.toColor(requireContext())
} else {
com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext())
}
)
mBinding.forumContainer.background =
if (mViewModel.communityEntity == null) R.drawable.button_round_primary_light.toDrawable(requireContext()) else com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999.toDrawable(

View File

@ -1,231 +1,34 @@
package com.gh.gamecenter.video.poster
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.PopupWindow
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity_TabLayout
import com.gh.gamecenter.common.base.fragment.BaseFragment_TabLayout
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.ActivityPosterEditBinding
import com.gh.gamecenter.entity.VideoEntity
import com.gh.gamecenter.video.poster.photo.PhotoAlbumsAdapter
import com.gh.gamecenter.video.poster.photo.PhotoAlbumsSpanner
import com.gh.gamecenter.video.poster.photo.PhotoPosterFragment
import com.gh.gamecenter.video.poster.video.VideoPosterFragment
import com.gh.gamecenter.video.upload.view.UploadVideoActivity
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.MimeType
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.SelectionSpec
import com.zhihu.matisse.internal.model.AlbumCollection
import java.io.File
import com.therouter.TheRouter
import com.therouter.router.Route
class PosterEditActivity : BaseActivity_TabLayout(), AdapterView.OnItemSelectedListener,
AlbumCollection.AlbumCallbacks {
private lateinit var mBinding: ActivityPosterEditBinding
private val mAlbumCollection = AlbumCollection()
private lateinit var mAlbumsSpinner: PhotoAlbumsSpanner
private lateinit var mAlbumsAdapter: PhotoAlbumsAdapter
private lateinit var mPhotoPosterFragment: PhotoPosterFragment
private lateinit var mVideoPosterFragment: VideoPosterFragment
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (UploadVideoActivity.REQUEST_CODE_IMAGE_CROP == requestCode && resultCode == Activity.RESULT_OK) {
setResult(Activity.RESULT_OK, data)
finish()
}
}
override fun provideTabView(position: Int, tabTitle: String?): View {
val tabCustomView = BaseFragment_TabLayout.createDefaultTabCustomView(this, tabTitle)
tabCustomView.findViewById<TextView>(R.id.tab_title)
.setTextColor(resources.getColorStateList(R.color.poster_text_tabbar_style))
return tabCustomView
}
override fun initFragmentList(fragments: MutableList<Fragment>) {
val videoPath = intent.getStringExtra(EntranceConsts.KEY_PATH_VIDEO)
val videoEntity = intent.getParcelableExtra<VideoEntity>(VideoEntity::class.java.simpleName)
mPhotoPosterFragment = PhotoPosterFragment()
mVideoPosterFragment = VideoPosterFragment.newInstance(videoPath, videoEntity)
fragments.add(mVideoPosterFragment)
fragments.add(mPhotoPosterFragment)
}
override fun initTabTitleList(tabTitleList: MutableList<String>) {
tabTitleList.add("视频截图")
tabTitleList.add("相册选择")
}
@Route(path = RouteConsts.activity.videoCoverEditActivity)
class PosterEditActivity : BaseActivity() {
override fun getLayoutId(): Int {
return R.layout.activity_poster_edit
return R.layout.actvitity_video_cover_edit
}
override fun onCreate(savedInstanceState: Bundle?) {
TheRouter.inject(this)
super.onCreate(savedInstanceState)
Matisse.from(this).choose(MimeType.ofImage()).showSingleMediaType(true)
DisplayUtils.setLightStatusBar(this, false)
setStatusBarColor(Color.TRANSPARENT)
setNavigationTitle("编辑封面")
initView()
initAlbumsSpinner()
}
private fun initView() {
mBinding = ActivityPosterEditBinding.bind(mContentView)
mBinding.activityViewPager.setScrollable(false)
mBinding.activityViewPager.addOnPageChangeListener {
if (VIDEO_POSTER_INDEX == it) {
setNavigationTitle("编辑封面")
mTitleTv.removeDrawable()
} else {
setPhotoNavigationTitle(false)
}
}
mBinding.confirm.setOnClickListener {
val selectImagePath = getSelectImagePath()
if (selectImagePath != null) {
if (mBinding.activityViewPager.currentItem == 0) {
val data = Intent()
data.putExtra(CropImageActivity.RESULT_CLIP_PATH, selectImagePath)
setResult(Activity.RESULT_OK, data)
finish()
} else {
val intent = CropImageActivity.getIntent(this, selectImagePath, 9 / 16F, mEntrance)
startActivityForResult(intent, UploadVideoActivity.REQUEST_CODE_IMAGE_CROP)
}
} else {
toast("请选择图片")
}
}
mTitleTv.setOnClickListener {
setPhotoNavigationTitle(true)
mAlbumsSpinner.show(mBinding.activityViewPager.height)
}
DisplayUtils.transparentStatusBar(this)
mBaseHandler.postDelayed({
mBinding.activityTabIndicator.generatePath(
mBinding.activityViewPager.currentItem,
0F
)
}, 10)
}
private fun initAlbumsSpinner() {
mAlbumsAdapter = PhotoAlbumsAdapter(this)
mAlbumsSpinner = PhotoAlbumsSpanner(this)
mAlbumsSpinner.setPopupAnchorView(findViewById(R.id.normal_toolbar))
mAlbumsSpinner.setAdapter(mAlbumsAdapter)
mAlbumsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
mAlbumCollection.setStateCurrentSelection(position)
mAlbumsAdapter.cursor.moveToPosition(position)
val album = Album.valueOf(mAlbumsAdapter.cursor)
if (album.isAll && SelectionSpec.getInstance().capture) {
album.addCaptureCount()
}
if (mPhotoPosterFragment.isAdded) {
mPhotoPosterFragment.loadPhotos(album)
}
}
}
mAlbumsSpinner.setDismissListener(PopupWindow.OnDismissListener {
setPhotoNavigationTitle(false)
})
mAlbumCollection.onCreate(this, this)
mAlbumCollection.loadAlbums()
}
private fun setPhotoNavigationTitle(up: Boolean) {
if (mBinding.activityViewPager.currentItem == VIDEO_POSTER_INDEX) return
mTitleTv.setDrawableEnd(if (up) R.drawable.poster_select_up else R.drawable.poster_select_down)
mTitleTv.compoundDrawablePadding = 2F.dip2px()
mTitleTv.text = "全部图片"
}
private fun getSelectImagePath(): String? {
if (mBinding.activityViewPager.currentItem == VIDEO_POSTER_INDEX) {
val clipPath = cacheDir.absolutePath + File.separator + System.currentTimeMillis() + ".jpg"
mVideoPosterFragment.savePicture(clipPath)
return clipPath
} else {
return mPhotoPosterFragment.getSelectImagePath()
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
}
override fun onAlbumLoad(cursor: Cursor?) {
mAlbumsAdapter.swapCursor(cursor)
mBaseHandler.post {
cursor?.moveToPosition(mAlbumCollection.currentSelection)
val album = Album.valueOf(cursor)
if (album.isAll && SelectionSpec.getInstance().capture) {
album.addCaptureCount()
}
if (::mPhotoPosterFragment.isInitialized && mPhotoPosterFragment.isAdded) {
mPhotoPosterFragment.loadPhotos(album)
}
}
}
override fun onAlbumReset() {
}
override fun provideNavigationIcon(): Int {
return com.gh.gamecenter.common.R.drawable.ic_toolbar_back_white
}
override fun handleBackPressed(): Boolean {
DialogHelper.showDialog(this, "提示", "确定放弃编辑封面吗?", "确定", "暂不", { finish() })
return true
}
companion object {
private const val VIDEO_POSTER_INDEX = 0
private const val PHOTO_POSTER_INDEX = 1
fun getIntentByPath(context: Context, videoPath: String): Intent {
val intent = Intent(context, PosterEditActivity::class.java)
intent.putExtra(EntranceConsts.KEY_PATH_VIDEO, videoPath)
return intent
}
fun getIntentByVideo(context: Context, videoEntity: VideoEntity): Intent {
val intent = Intent(context, PosterEditActivity::class.java)
intent.putExtra(VideoEntity::class.java.simpleName, videoEntity)
return intent
}
val tag = VideoPosterFragment::class.java.name
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: VideoPosterFragment.newInstance()
fragment.arguments = intent.extras
supportFragmentManager
.beginTransaction()
.replace(R.id.fcv_container, fragment, tag)
.commitNowAllowingStateLoss()
}
}

View File

@ -0,0 +1,91 @@
package com.gh.gamecenter.video.poster
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.gh.gamecenter.common.utils.dip2px
class PreviewMaskView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) :
View(context, attrs, defStyleAttr, defStyleRes) {
private var isLight = false
private var leftTopCorner = 0F
private var rightTopCorner = 0F
private var leftBottomCorner = 0F
private var rightBottomCorner = 0F
private val paint by lazy {
Paint().apply {
isAntiAlias = true
isDither = true
}
}
val defaultColor = Color.parseColor("#40000000")
val lightColor = Color.parseColor("#000000")
private val xFermode by lazy {
PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
private val rectF = RectF()
private var path = Path()
fun setCorners(leftTop: Float, rightTop: Float, rightBottom: Float, leftBottom: Float) {
leftTopCorner = leftTop
rightTopCorner = rightTop
rightBottomCorner = rightBottom
leftBottomCorner = leftBottom
postInvalidateOnAnimation()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
rectF.set(0F, 0F, w.toFloat(), h.toFloat())
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (isLight) {
paint.color = lightColor
val layerId = canvas.saveLayerAlpha(rectF, 64)
drawBackground(canvas)
paint.xfermode = xFermode
canvas.drawRoundRect(rectF, 4F.dip2px().toFloat(), 4F.dip2px().toFloat(), paint)
paint.xfermode = null
canvas.restoreToCount(layerId)
} else {
paint.color = defaultColor
drawBackground(canvas)
}
}
private fun drawBackground(canvas: Canvas) {
path.reset()
val radii = floatArrayOf(
leftTopCorner,
leftTopCorner,
rightTopCorner,
rightTopCorner,
rightBottomCorner,
rightBottomCorner,
leftBottomCorner,
leftBottomCorner,
)
path.addRoundRect(rectF, radii, Path.Direction.CW)
canvas.drawPath(path, paint)
}
fun setLight(isLight: Boolean) {
this.isLight = isLight
postInvalidateOnAnimation()
}
}

View File

@ -1,75 +1,282 @@
package com.gh.gamecenter.video.poster.video
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.throwExceptionInDebug
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.databinding.FragmentVideoPosterBinding
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.PermissionHelper.checkStoragePermissionBeforeAction
import com.gh.gamecenter.common.view.cropbox.CropBoxStyle
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.FragmentVideoCoverEditBinding
import com.gh.gamecenter.entity.VideoEntity
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.video.poster.video.VideoPosterViewModel.Companion.SELECTED_IMAGE_POSITION
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import com.halo.assistant.HaloApp
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.internal.utils.PathUtils
import java.io.File
import java.io.FileOutputStream
class VideoPosterFragment : BaseFragment<Any>() {
private lateinit var mBinding: FragmentVideoPosterBinding
private lateinit var mAdapter: VideoPosterReviewAdapter
private lateinit var binding: FragmentVideoCoverEditBinding
private lateinit var viewModel: VideoPosterViewModel
private lateinit var mViewModel: VideoPosterViewModel
var videoPath: String? = null
override fun getLayoutId(): Int {
return R.layout.fragment_video_poster
var videoEntity: VideoEntity? = null
private lateinit var adapter: VideoPosterReviewAdapter
private lateinit var takePhotoLauncher: ActivityResultLauncher<Unit>
override fun getInflatedLayout(): View {
return View(requireContext())
}
override fun getLayoutId(): Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
videoPath = arguments?.getString(EntranceConsts.KEY_PATH_VIDEO)
videoEntity = arguments?.getParcelable<VideoEntity>(EntranceConsts.KEY_VIDEO_ENTITY)
super.onCreate(savedInstanceState)
mBinding = FragmentVideoPosterBinding.bind(mCachedView)
val videoPath = arguments?.getString(EntranceConsts.KEY_PATH_VIDEO)
val videoEntity = arguments?.getParcelable<VideoEntity>(VideoEntity::class.java.simpleName)
val videoCoverCache =
SPUtils.getString(Constants.SP_VIDEO_COVER_CACHE).toObject<VideoPosterViewModel.VideoCoverCache>()
val factory =
VideoPosterViewModel.Factory(HaloApp.getInstance().application, videoPath, videoEntity, videoCoverCache)
viewModel = ViewModelProviders.of(this, factory).get(VideoPosterViewModel::class.java)
val factory = VideoPosterViewModel.Factory(HaloApp.getInstance().application, videoPath, videoEntity)
mViewModel = ViewModelProviders.of(this, factory).get(VideoPosterViewModel::class.java)
mViewModel.videoPreviewsLiveData.observeNonNull(this, callback = {
mAdapter.submitList(it)
})
mViewModel.previewLiveData.observeNonNull(this, callback = {
if (it.thumbs != null) {
mBinding.receiveIv.setBitmap(it.thumbs)
} else if (it.thumbsUrl != null) {
ImageUtils.picasso
.load(Uri.parse(it.thumbsUrl))
.into(mBinding.receiveIv.cropImageZoomView)
takePhotoLauncher = registerForActivityResult(object : ActivityResultContract<Unit, Intent?>() {
override fun createIntent(context: Context, input: Unit): Intent {
return LocalMediaActivity.getIntent(
context,
ChooseType.IMAGE,
1,
"视频封面编辑"
)
}
})
override fun parseResult(resultCode: Int, intent: Intent?): Intent? {
return intent
}
}) {
if (it != null) {
val uris = Matisse.obtainResult(it)
if (uris.isNotEmpty()) {
val path = PathUtils.getPath(requireContext(), uris.first())
if (path != null) {
val ivZoom = binding.cropImageCustom.cropImageZoomView
val bitmap = BitmapUtils.getBitmapByFile(path, ivZoom.width, ivZoom.height)
if (bitmap != null) {
val videoPreview = VideoPosterViewModel.VideoPreview(0, bitmap, null)
viewModel.setSelectedImage(videoPreview)
}
}
}
}
}
}
override fun initView(view: View?) = Unit
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return FragmentVideoCoverEditBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.receiveIv.setCropRatio(9 / 16F)
mBinding.reviewList.layoutManager = GridLayoutManager(requireContext(), 10)
mAdapter = VideoPosterReviewAdapter(requireContext(), mViewModel)
mBinding.reviewList.adapter = mAdapter
adapter = VideoPosterReviewAdapter(requireContext(), viewModel)
binding.rvImages.layoutManager = GridLayoutManager(requireContext(), 10)
binding.rvImages.adapter = adapter
binding.ivTakePhoto.shapeAppearanceModel = ShapeAppearanceModel()
.toBuilder()
.setAllCorners(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
.build()
binding.ivTakePhoto.clipToOutline = true
binding.ivPreviewBorder.shapeAppearanceModel = ShapeAppearanceModel()
.toBuilder()
.setAllCorners(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
.build()
binding.ivPreviewBorder.strokeWidth = 4F.dip2px().toFloat()
binding.ivPreviewBorder.strokeColor = ColorStateList.valueOf(Color.parseColor("#2496FF"))
binding.ivPreviewBorder.clipToOutline = true
binding.cropImageCustom.setCropBoxStyle(CropBoxStyle.Rectangle(23F / 41))
binding.ivTakePhoto.setOnClickListener {
val videoPreview = viewModel.selectedImage.value
if (videoPreview == null) {
checkStoragePermissionBeforeAction(requireContext()) { takePhotoLauncher.launch(Unit) }
} else {
viewModel.setPreviewImage(SELECTED_IMAGE_POSITION, videoPreview)
}
}
binding.ivRemove.setOnClickListener {
viewModel.setSelectedImage(null)
if (viewModel.previewPosition.value == SELECTED_IMAGE_POSITION) {
adapter.getItem(0)?.let {
viewModel.setPreviewImage(0, it)
}
}
}
binding.tvCancel.setOnClickListener {
activity?.finish()
}
binding.tvSubmit.setOnClickListener {
activity?.let {
val clipPath = it.cacheDir.absolutePath + File.separator + System.currentTimeMillis() + ".jpg"
binding.cropImageCustom.savePicture(clipPath)
saveVideoOriginalCoverToLocal(it)
val data = Intent()
data.putExtra(CropImageActivity.RESULT_CLIP_PATH, clipPath)
it.setResult(Activity.RESULT_OK, data)
it.finish()
}
}
with(viewModel) {
videoPreviewsLiveData.observeNonNull(viewLifecycleOwner, callback = {
adapter.submitList(it)
})
previewLiveData.observeNonNull(viewLifecycleOwner, callback = {
binding.cropImageCustom.reset()
it.values?.let(binding.cropImageCustom::setTransformationValues)
if (it.thumbs != null) {
binding.cropImageCustom.setBitmap(it.thumbs)
} else if (it.thumbsUrl != null) {
ImageUtils.picasso
.load(Uri.parse(it.thumbsUrl))
.into(binding.cropImageCustom.cropImageZoomView)
}
})
selectedImage.observe(viewLifecycleOwner) {
if (it == null) {
binding.ivTakePhoto.setImageResource(R.drawable.ic_video_cover_edit_take_photo)
} else {
binding.ivTakePhoto.setImageBitmap(it.thumbs)
}
binding.ivRemove.goneIf(it == null)
}
previewPosition.observe(viewLifecycleOwner) {
setSelectedImageSelected(it == SELECTED_IMAGE_POSITION)
adapter.setPreviewPosition(it)
}
}
}
fun savePicture(path: String) {
if (isAdded) throwExceptionInDebug("save clip picture failure", !mBinding.receiveIv.savePicture(path))
/**
* 将封面原图保存在本地,以便下次编辑时显示
*/
private fun saveVideoOriginalCoverToLocal(context: Context) {
val preview = viewModel.previewLiveData.value
if (preview != null) {
val position = viewModel.previewPosition.value ?: -2
var cache: VideoPosterViewModel.VideoCoverCache? = null
val values = binding.cropImageCustom.transformationValues
if (preview.thumbs != null) {
val path = saveToInternalStorage(context, preview.thumbs)
if (path != null) {
cache = VideoPosterViewModel.VideoCoverCache(position, localPath = path, values = values)
}
} else if (preview.thumbsUrl != null) {
cache = VideoPosterViewModel.VideoCoverCache(position, url = preview.thumbsUrl, values = values)
}
val json = cache?.toJson()
if (json != null) {
SPUtils.setString(Constants.SP_VIDEO_COVER_CACHE, json)
}
}
}
private fun setSelectedImageSelected(isSelected: Boolean) {
if (isSelected) {
binding.ivTakePhoto.strokeWidth = 6F.dip2px().toFloat()
binding.ivTakePhoto.strokeColor = ColorStateList.valueOf(Color.parseColor("#232323"))
} else {
binding.ivTakePhoto.strokeWidth = 0F
}
binding.ivPreviewBorder.goneIf(!isSelected)
}
// 保存到内部存储
private fun saveToInternalStorage(
context: Context,
bitmap: Bitmap
): String? {
try {
val file = createVideoCoverFile(context)
// 将图片保存到文件
FileOutputStream(file).use { fos ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
fos.flush()
}
return file.absolutePath
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
companion object {
fun newInstance(videoPath: String?, videoEntity: VideoEntity?): VideoPosterFragment {
val fragment = VideoPosterFragment()
val bundle = Bundle()
bundle.putString(EntranceConsts.KEY_PATH_VIDEO, videoPath)
bundle.putParcelable(VideoEntity::class.java.simpleName, videoEntity)
fragment.arguments = bundle
return fragment
private const val VIDEO_COVER_DIRECTORY_NAME = "videoCover"
fun createVideoCoverFile(context: Context): File {
// 获取应用私有目录
val directory = context.getDir(VIDEO_COVER_DIRECTORY_NAME, Context.MODE_PRIVATE)
// 创建文件
return File(directory, "cover_image.jpg")
}
/**
* 清理上次裁剪图片留下的缓存文件
*/
fun clearVideoCoverCache(context: Context) {
SPUtils.setString(Constants.SP_VIDEO_COVER_CACHE, "")
context.getDir(VIDEO_COVER_DIRECTORY_NAME, Context.MODE_PRIVATE).let { directory ->
directory.listFiles()?.forEach { it.delete() }
}
}
fun newInstance() = VideoPosterFragment()
}
}

View File

@ -1,15 +1,19 @@
package com.gh.gamecenter.video.poster.video
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.VideoPosterPreviewItemBinding
import com.gh.gamecenter.video.poster.video.VideoPosterViewModel.Companion.SELECTED_IMAGE_POSITION
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import com.lightgame.adapter.BaseRecyclerAdapter
class VideoPosterReviewAdapter(
@ -19,11 +23,7 @@ class VideoPosterReviewAdapter(
private var mListData: MutableList<VideoPosterViewModel.VideoPreview> = ArrayList()
private val mSelectMap = HashMap<Int, Boolean>()
init {
mSelectMap[0] = true // default select
}
private var selectedPosition = 0
fun submitList(listData: List<VideoPosterViewModel.VideoPreview>) {
mListData = listData.toMutableList()
@ -41,6 +41,22 @@ class VideoPosterReviewAdapter(
override fun onBindViewHolder(holder: VideoPosterReviewItemViewHolder, position: Int) {
val builder = ShapeAppearanceModel().toBuilder()
when (position) {
0 -> builder.setTopLeftCorner(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
.setTopRightCorner(CornerFamily.ROUNDED, 0F)
.setBottomRightCorner(CornerFamily.ROUNDED, 0F)
.setBottomLeftCorner(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
itemCount - 1 -> builder.setTopLeftCorner(CornerFamily.ROUNDED, 0F)
.setTopRightCorner(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
.setBottomRightCorner(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
.setBottomLeftCorner(CornerFamily.ROUNDED, 0F)
else -> builder.setAllCorners(CornerFamily.ROUNDED, 0F)
}
holder.binding.preview.shapeAppearanceModel = builder.build()
val videoPreview = mListData[position]
val thumbs = videoPreview.thumbs
if (thumbs != null) {
@ -51,41 +67,67 @@ class VideoPosterReviewAdapter(
.into(holder.binding.preview)
}
holder.binding.preview.setOnClickListener {
viewModel.previewLiveData.postValue(videoPreview)
viewModel.setPreviewImage(position, mListData[position])
}
setSelected(holder, position)
mSelectMap[position] = true
for (entry in mSelectMap) {
if (entry.value && entry.key != position) {
mSelectMap[entry.key] = false
notifyItemChanged(entry.key)
}
}
override fun onBindViewHolder(holder: VideoPosterReviewItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(PAYLOADS_IS_SELECTED)) {
setSelected(holder, position)
} else {
super.onBindViewHolder(holder, position, payloads)
}
}
private fun setSelected(holder: VideoPosterReviewItemViewHolder, position: Int) {
holder.binding.preview.clipToOutline = true
holder.binding.previewBorderInner.clipToOutline = true
holder.binding.previewBorderOut.shapeAppearanceModel = ShapeAppearanceModel().toBuilder()
.setAllCorners(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
.build()
holder.binding.previewBorderOut.strokeColor = ColorStateList.valueOf(Color.parseColor("#232323"))
holder.binding.previewBorderOut.strokeWidth = 6F.dip2px().toFloat()
holder.binding.previewBorderOut.clipToOutline = true
holder.binding.previewBorderInner.shapeAppearanceModel = ShapeAppearanceModel().toBuilder()
.setAllCorners(CornerFamily.ROUNDED, 4F.dip2px().toFloat())
.build()
holder.binding.previewBorderInner.strokeColor = ColorStateList.valueOf(Color.parseColor("#2496FF"))
holder.binding.previewBorderInner.strokeWidth = 4F.dip2px().toFloat()
holder.binding.gPreviewBorder.goneIf(selectedPosition != position)
fun getCorner(hasCorner: Boolean) = if (hasCorner) 4F.dip2px().toFloat() else 0F
val leftCorner = getCorner(position == 0)
val rightCorner = getCorner(position == itemCount - 1)
holder.binding.vCover.setCorners(
leftCorner,
rightCorner,
rightCorner,
leftCorner,
)
holder.binding.vCover.setLight(selectedPosition == position)
}
fun setPreviewPosition(position: Int) {
if (selectedPosition != position) {
val lastPosition = selectedPosition
selectedPosition = position
if (lastPosition != SELECTED_IMAGE_POSITION) {
notifyItemChanged(lastPosition, PAYLOADS_IS_SELECTED)
}
if (selectedPosition != SELECTED_IMAGE_POSITION) {
notifyItemChanged(position, PAYLOADS_IS_SELECTED)
}
notifyItemChanged(position)
}
}
holder.binding.previewContainer.radius = if (mSelectMap[position] == true) {
3F.dip2px().toFloat()
} else if (position == 0 || position == itemCount - 1) {
4F.dip2px().toFloat()
} else {
0F
}
holder.binding.previewBorder.visibility = if (mSelectMap[position] == true) {
View.VISIBLE
} else View.GONE
fun getItem(position: Int) = mListData.getOrNull(position)
val previewContainerLp = holder.binding.previewContainer.layoutParams as ConstraintLayout.LayoutParams
if (position == 0) {
previewContainerLp.rightMargin = (-4F).dip2px()
previewContainerLp.leftMargin = 0
} else if (position == itemCount - 1) {
previewContainerLp.leftMargin = (-4F).dip2px()
previewContainerLp.rightMargin = 0
} else {
previewContainerLp.rightMargin = 0
previewContainerLp.leftMargin = 0
}
holder.binding.previewContainer.layoutParams = previewContainerLp
companion object {
private const val PAYLOADS_IS_SELECTED = "payloads_is_selected"
}
}

View File

@ -4,9 +4,11 @@ import android.app.Application
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import androidx.lifecycle.*
import com.gh.gamecenter.common.utils.BitmapUtils
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.throwExceptionInDebug
import com.gh.gamecenter.common.utils.tryCatchInRelease
import com.gh.gamecenter.common.view.CropImageZoomView.TransformationValues
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.entity.VideoEntity
import java.io.File
@ -15,13 +17,33 @@ import kotlin.math.roundToLong
class VideoPosterViewModel(
application: Application,
val videoPath: String?,
val videoEntity: VideoEntity?
val videoEntity: VideoEntity?,
val cache: VideoCoverCache?
) : AndroidViewModel(application) {
val videoPreviewsLiveData = MutableLiveData<List<VideoPreview>>()
val previewLiveData = MutableLiveData<VideoPreview>()
private val _selectedImage = MutableLiveData<VideoPreview?>()
val selectedImage: LiveData<VideoPreview?> = _selectedImage
fun setSelectedImage(videoPreview: VideoPreview?) {
_selectedImage.value = videoPreview
if (videoPreview != null) {
setPreviewImage(SELECTED_IMAGE_POSITION, videoPreview)
}
}
private val _previewPosition = MutableLiveData<Int>()
val previewPosition: LiveData<Int> = _previewPosition
fun setPreviewImage(position: Int, videoPreview: VideoPreview) {
val oldPosition = _previewPosition.value
if (oldPosition != position) {
_previewPosition.value = position
previewLiveData.value = videoPreview
}
}
init {
if (videoPath != null) {
@ -36,22 +58,35 @@ class VideoPosterViewModel(
}
val videoReviews = ArrayList<VideoPreview>()
val cachePreview = cache?.toVideoPreview()
for (i in 0 until totalThumbsCount) {
val frameTime: Long = (startPosition + interval * i).roundToLong()
val thumbsUrl = ImageUtils.getVideoSnapshot(videoEntity.url, frameTime * 1000)
val element = VideoPreview(frameTime, null, thumbsUrl)
videoReviews.add(element)
if (videoReviews.size == 1) {
if (cachePreview == null && videoReviews.size == 1) {
previewLiveData.postValue(element)
}
}
videoPreviewsLiveData.postValue(videoReviews)
cachePreview?.let(::initFromCache)
} else {
throwExceptionInDebug("video not found")
}
}
private fun initFromCache(cachePreview: VideoPreview) {
cache ?: return
previewLiveData.postValue(cachePreview)
_previewPosition.postValue(cache.position)
if (cache.position == SELECTED_IMAGE_POSITION) {
_selectedImage.value = cachePreview
}
}
private fun getImagesByVideo() {
throwExceptionInDebug("video file not found", !File(videoPath).exists())
@ -68,6 +103,7 @@ class VideoPosterViewModel(
return
}
val cachePreview = cache?.toVideoPreview()
val duration = durationString.toLong()
val interval = (duration / totalThumbsCount)
val videoReviews = ArrayList<VideoPreview>()
@ -88,23 +124,64 @@ class VideoPosterViewModel(
val element = VideoPreview(frameTime * 1000, bitmap)
videoReviews.add(element)
videoPreviewsLiveData.postValue(videoReviews)
if (videoReviews.size == 1) {
if (cachePreview == null && videoReviews.size == 1) {
previewLiveData.postValue(element)
}
}
cachePreview?.let(::initFromCache)
mediaMetadataRetriever.release()
}
class VideoPreview(val time: Long, val thumbs: Bitmap? = null, val thumbsUrl: String? = null)
companion object {
const val SELECTED_IMAGE_POSITION = -1
}
class VideoPreview(
val time: Long,
val thumbs: Bitmap? = null,
val thumbsUrl: String? = null,
val values: TransformationValues? = null
)
class VideoCoverCache(
val position: Int,
val localPath: String? = null,
val url: String? = null,
val values: TransformationValues
) {
fun toVideoPreview(): VideoPreview? =
when {
localPath != null -> {
if (File(localPath).exists()) {
val bitmap = BitmapUtils.getBitmapByFile(localPath, Bitmap.Config.RGB_565)
if (bitmap != null) {
VideoPreview(0, bitmap, null, values)
} else {
null
}
} else {
null
}
}
url != null -> VideoPreview(0, null, url, values)
else -> null
}
}
class Factory(
private val mApplication: Application,
private val mVideoPath: String?,
private val mVideoEntity: VideoEntity?
private val mVideoEntity: VideoEntity?,
private val cache: VideoCoverCache?
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return VideoPosterViewModel(mApplication, mVideoPath, mVideoEntity) as T
return VideoPosterViewModel(mApplication, mVideoPath, mVideoEntity, cache) as T
}
}
}

View File

@ -36,6 +36,7 @@ import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.common.callback.CancelListener
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.NotificationUgc
import com.gh.gamecenter.common.entity.SimpleGameEntity
import com.gh.gamecenter.common.mvvm.Status
@ -49,12 +50,13 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.newsdetail.NewsDetailActivity
import com.gh.gamecenter.qa.editor.GameActivity
import com.gh.gamecenter.video.label.VideoLabelActivity
import com.gh.gamecenter.video.poster.PosterEditActivity
import com.gh.gamecenter.video.poster.video.VideoPosterFragment
import com.gh.gamecenter.video.upload.OnUploadListener
import com.gh.gamecenter.video.upload.UploadManager
import com.google.android.flexbox.FlexboxLayout
import com.lightgame.utils.Util_System_Keyboard
import com.lightgame.utils.Utils
import com.therouter.TheRouter
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.internal.utils.PathUtils
import org.json.JSONArray
@ -96,25 +98,6 @@ class UploadVideoActivity : ToolBarActivity() {
mBinding.gameName.text = game.name
mBinding.gameName.setTextColor(resources.getColor(com.gh.gamecenter.common.R.color.text_primary))
}
} else if (requestCode == REQUEST_CODE_IMAGE_STORE && resultCode == Activity.RESULT_OK) {
if (data != null) {
val uris = Matisse.obtainResult(data)
for (uri in uris) {
val picturePath = PathUtils.getPath(application, uri)
if (File(picturePath).length() > ImageUtils.getUploadFileMaxSize()) {
val count = ImageUtils.getUploadFileMaxSize() / 1024 / 1024
val application: Application = application
Utils.toast(getApplication(), application.getString(com.gh.gamecenter.common.R.string.pic_max_hint, count))
continue
}
Utils.log("picturePath = $picturePath")
// skip image crop
val intent = CropImageActivity.getIntent(this@UploadVideoActivity, picturePath, 9 / 16F, mEntrance)
startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
break
}
}
} else if (requestCode == REQUEST_CODE_IMAGE_CROP && resultCode == Activity.RESULT_OK) {
if (data != null) {
val imagePath = data.getStringExtra(CropImageActivity.RESULT_CLIP_PATH)
@ -136,6 +119,7 @@ class UploadVideoActivity : ToolBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
VideoPosterFragment.clearVideoCoverCache(this)
mVideoLink = intent.getParcelableExtra(VideoLinkEntity::class.java.simpleName)
mEntranceLink = intent.getStringExtra(EntranceConsts.KEY_ENTRANCE_LINK) ?: ""
mPath = intent.getStringExtra(EntranceConsts.KEY_PATH) ?: "其他"
@ -259,7 +243,8 @@ class UploadVideoActivity : ToolBarActivity() {
R.drawable.ic_upload_video_activity_unenable
)
)
mBinding.chooseActivityIv.background = ContextCompat.getDrawable(this, com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999)
mBinding.chooseActivityIv.background =
ContextCompat.getDrawable(this, com.gh.gamecenter.common.R.drawable.bg_shape_f5_radius_999)
}
}
@ -429,14 +414,7 @@ class UploadVideoActivity : ToolBarActivity() {
private fun startMediaStore() {
try {
PermissionHelper.checkStoragePermissionBeforeAction(this) {
// Matisse.from(this@UploadVideoActivity)
// .choose(MimeType.ofImage())
// .showSingleMediaType(true)
// .countable(true)
// .addFilter(GhMatisseFilter())
// .maxSelectable(1)
// .forResult(REQUEST_CODE_IMAGE_STORE)
val postcard = TheRouter.build(RouteConsts.activity.videoCoverEditActivity)
val videoPath = if (mViewModel.videoDraft != null) {
mViewModel.videoDraft?.localPath
@ -444,22 +422,17 @@ class UploadVideoActivity : ToolBarActivity() {
intent.getStringExtra(EntranceConsts.KEY_PATH_VIDEO)
}
var intent: Intent? = null
if (!videoPath.isNullOrEmpty()) {
intent = PosterEditActivity.getIntentByPath(this@UploadVideoActivity, videoPath)
postcard.withString(EntranceConsts.KEY_PATH_VIDEO, videoPath)
} else {
val videoPatch = mViewModel.videoPatch
if (videoPatch != null) {
intent = PosterEditActivity.getIntentByVideo(this@UploadVideoActivity, videoPatch)
postcard.withParcelable(EntranceConsts.KEY_VIDEO_ENTITY, videoPatch)
} else {
throwExceptionInDebug("video not found")
}
}
if (intent != null) {
startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
} else {
toast("找不到相关视频")
}
postcard.navigation(this, REQUEST_CODE_IMAGE_CROP)
}
} catch (e: Exception) {
toast(R.string.media_image_hint)
@ -672,8 +645,16 @@ class UploadVideoActivity : ToolBarActivity() {
flexCell.gravity = Gravity.CENTER
flexCell.text = videoTagEntity.name
flexCell.tag = videoTagEntity.id
flexCell.setTextColor(DrawableView.getSelectorColorStyle(com.gh.gamecenter.common.R.color.text_secondary, com.gh.gamecenter.common.R.color.white))
flexCell.background = DrawableView.getOvalSelectorStyle(com.gh.gamecenter.common.R.color.text_f2f2f2, com.gh.gamecenter.common.R.color.primary_theme)
flexCell.setTextColor(
DrawableView.getSelectorColorStyle(
com.gh.gamecenter.common.R.color.text_secondary,
com.gh.gamecenter.common.R.color.white
)
)
flexCell.background = DrawableView.getOvalSelectorStyle(
com.gh.gamecenter.common.R.color.text_f2f2f2,
com.gh.gamecenter.common.R.color.primary_theme
)
flexCell.setPadding(12F.dip2px(), 0, 12F.dip2px(), 0)
flexCell.setOnClickListener {
Util_System_Keyboard.hideSoftKeyboard(this, mBinding.gameTitle)
@ -789,7 +770,11 @@ class UploadVideoActivity : ToolBarActivity() {
mBinding.uploadSpeed.visibility = View.VISIBLE
mBinding.uploadButton.visibility = View.VISIBLE
mBinding.uploadSpeed.text =
(SpeedUtils.getSpeed(speed) + "预计还需" + SpeedUtils.getRemainTime(totalSize, currentSize, speed))
(SpeedUtils.getSpeed(speed) + "预计还需" + SpeedUtils.getRemainTime(
totalSize,
currentSize,
speed
))
mBinding.uploadProgress.update(((360 * currentSize) / totalSize).toInt(), "")
}
}
@ -941,7 +926,6 @@ class UploadVideoActivity : ToolBarActivity() {
private const val REQUEST_GAME_CODE = 116
const val RESULT_CODE_VIDEO = 117
const val RESULT_CODE_DRAFT = 118
const val REQUEST_CODE_IMAGE_STORE = 119
const val REQUEST_CODE_IMAGE_CROP = 120
const val REQUEST_CODE_CHOOSE_LABEL = 121

View File

@ -43,6 +43,8 @@ import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.observer.MuteCallback
import com.gh.gamecenter.common.observer.VolumeObserver
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.TabIndicatorView
import com.gh.gamecenter.core.iinterface.IScrollable
@ -162,6 +164,20 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
private val twoLinesTabToolbarHeight = 48F.dip2px()
private var autoVideoView: AutomaticVideoView? = null
private val volumeObserver by lazy {
VolumeObserver(object : MuteCallback {
override fun onMute(isMute: Boolean) {
if (::binding.isInitialized) {
binding.muteIv.setImageResource(if (isMute) R.drawable.ic_basic_offsound else R.drawable.ic_basic_onsound)
}
if (isMute) {
autoVideoView?.mute()
} else {
autoVideoView?.unMute()
}
}
})
}
private var searchStyle: BottomTab.SearchStyle? = null
set(value) {
@ -552,6 +568,8 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
oldState: RefreshState,
newState: RefreshState
) {
getValidSmartRefreshContent()?.onStateChanged(refreshLayout, oldState, newState)
val isAutoScrollToTwoLevel = oldState == RefreshState.None && newState == RefreshState.TwoLevel
val isDragToTwoLevel =
oldState == RefreshState.ReleaseToRefresh && newState == RefreshState.ReleaseToTwoLevel
@ -649,49 +667,17 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
if (isFinishTwoLevel) {
pausePullDownPushVideo()
showTwoLevel = false
if (isDragging) {
autoFinishTwoLevelHandler?.removeMessages(KEY_AUTO_FINISH_TWO_LEVEL)
elapsedHelper.pauseCounting()
pullDownPush?.run {
SensorsBridge.trackEvent(
"DropDownPushClick",
json {
"action" to pullDownPushAction
"button_name" to "关闭推送"
"drop_down_push_id" to id
"game_name" to game?.name
"game_id" to game?.id
"bottom_tab" to bottomTabName
"several_tab_page_name" to multiTabNavName
"several_tab_page_id" to multiTabNavId
"position" to lastSelectedPosition
"tab_content" to currentTabEntity?.name
"custom_page_name" to currentTabEntity?.link?.text
"custom_page_id" to currentTabEntity?.link?.link
}
)
com.gh.common.util.NewFlatLogUtils.logHomePushClose(
id,
"主动收起",
twoLevelOpenCount,
game?.id ?: "",
game?.name ?: "",
elapsedHelper.elapsedTime
)
}
finishTwoLevel("主动收起")
}
showTwoLevel = false
}
when (newState) {
RefreshState.TwoLevel -> {
getValidSmartRefreshContent()?.setScrollEnabled(false)
binding.refreshLayout.isDisableContent = false
}
RefreshState.None -> {
getValidSmartRefreshContent()?.setScrollEnabled(true)
showTwoLevel = false
isDragging = false
twoLevelHeader.setFloorDuration(1000)
@ -935,6 +921,7 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
classicsHeader.visibility = View.INVISIBLE
pullDownPushSet.add(id)
SPUtils.setStringSet(Constants.SP_PULL_DOWN_PUSH_POP_UP_SET, pullDownPushSet)
autoFinishTwoLevelCallback = finishCallback
autoFinishTwoLevelHandler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
@ -956,9 +943,6 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
val autoFinishDelayTime = putAwaySwitch.toInt() * 1000L
autoFinishTwoLevelHandler?.sendEmptyMessageDelayed(KEY_AUTO_FINISH_TWO_LEVEL, autoFinishDelayTime)
}
else -> {
autoFinishTwoLevelCallback = finishCallback
}
}
}
}
@ -1033,6 +1017,7 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
ImageUtils.display(gameImageIv, imgUrl)
}
autoVideoView?.onVideoReset()
muteIv.isVisible = video != null
autoVideoView?.goneIf(video == null) {
if (autoVideoView?.isInPlayingState == false) {
GSYVideoOptionBuilder()
@ -1051,6 +1036,17 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
headerContainer.performClick()
}
}
var videoVoiceStatus = SPUtils.getBoolean(Constants.SP_VIDEO_PLAY_MUTE, true)
muteIv.setImageResource(if (videoVoiceStatus) R.drawable.ic_basic_offsound else R.drawable.ic_basic_onsound)
muteIv.setDebouncedClickListener {
videoVoiceStatus = !videoVoiceStatus
muteIv.setImageResource(if (videoVoiceStatus) R.drawable.ic_basic_offsound else R.drawable.ic_basic_onsound)
if (videoVoiceStatus) {
autoVideoView?.mute()
} else {
autoVideoView?.unMute()
}
}
}
}
}
@ -1065,6 +1061,19 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
pullDownPushHandler?.doPreProcess(this as? ISmartRefresh, popupPush)
}
private fun observeVolume() {
context?.applicationContext?.contentResolver?.registerContentObserver(
android.provider.Settings.System.CONTENT_URI,
true,
volumeObserver
)
}
private fun unObserveVolume() {
context?.applicationContext?.contentResolver?.unregisterContentObserver(volumeObserver)
}
private fun updateRefreshHeaderStyle(force: Boolean = false) {
if (lightRefreshHeaderStyle == isDisplayingLightContent && !force) return
lightRefreshHeaderStyle = isDisplayingLightContent
@ -1109,6 +1118,7 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
} else {
resumePullDownPushVideo()
}
observeVolume()
}
}
}
@ -1122,6 +1132,7 @@ class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbar
if (!videoUrl.isNullOrEmpty()) {
ScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(videoUrl), currentPosition)
}
unObserveVolume()
}
}
}

View File

@ -21,6 +21,8 @@ import com.gh.gamecenter.entity.MultiTabNav
import com.gh.gamecenter.entity.PullDownPush
import com.gh.gamecenter.feature.utils.SentryHelper
import com.google.android.material.tabs.TabLayout
import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.scwang.smartrefresh.layout.constant.RefreshState
import kotlin.math.roundToInt
/**
@ -148,10 +150,6 @@ class TabWrapperFragment: BaseTabWrapperFragment(), ISmartRefresh, ISmartRefresh
}
}
override fun setScrollEnabled(isScrollEnabled: Boolean) {
getValidSmartRefreshContent()?.setScrollEnabled(isScrollEnabled)
}
override fun onRefresh() {
getValidSmartRefreshContent()?.onRefresh()
}
@ -161,6 +159,10 @@ class TabWrapperFragment: BaseTabWrapperFragment(), ISmartRefresh, ISmartRefresh
getCurrentTabEntity()?.showPullDownPush = !isSwipeRefreshEnabled
}
override fun onStateChanged(refreshLayout: RefreshLayout, oldState: RefreshState, newState: RefreshState) {
getValidSmartRefreshContent()?.onStateChanged(refreshLayout, oldState, newState)
}
override fun setSmartRefreshEnabled(isEnable: Boolean) {
(parentFragment as? ISmartRefresh)?.setSmartRefreshEnabled(isEnable)
}

View File

@ -41,6 +41,8 @@ import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.observer.MuteCallback
import com.gh.gamecenter.common.observer.VolumeObserver
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.MD5Utils
@ -108,6 +110,20 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
private var mLightToolbar = false
private var mAutoFinishTwoLevelHandler: Handler? = null
private var mAutoFinishTwoLevelCallback: (() -> Unit)? = null
private val mVolumeObserver by lazy {
VolumeObserver(object : MuteCallback {
override fun onMute(isMute: Boolean) {
if (::mBinding.isInitialized) {
mBinding.muteIv.setImageResource(if (isMute) R.drawable.ic_basic_offsound else R.drawable.ic_basic_onsound)
if (isMute) {
mBinding.autoVideoView.mute()
} else {
mBinding.autoVideoView.unMute()
}
}
}
})
}
private lateinit var mElapsedHelper: TimeElapsedHelper
@ -227,6 +243,7 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
gameImageIv.goneIf(video != null) {
ImageUtils.display(gameImageIv, imgUrl)
}
muteIv.isVisible = video != null
autoVideoView.goneIf(video == null) {
if (!autoVideoView.isInPlayingState) {
GSYVideoOptionBuilder()
@ -258,6 +275,17 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
headerContainer.performClick()
}
}
var videoVoiceStatus = SPUtils.getBoolean(Constants.SP_VIDEO_PLAY_MUTE, true)
muteIv.setImageResource(if (videoVoiceStatus) R.drawable.ic_basic_offsound else R.drawable.ic_basic_onsound)
muteIv.setDebouncedClickListener {
videoVoiceStatus = !videoVoiceStatus
muteIv.setImageResource(if (videoVoiceStatus) R.drawable.ic_basic_offsound else R.drawable.ic_basic_onsound)
if (videoVoiceStatus) {
autoVideoView.mute()
} else {
autoVideoView.unMute()
}
}
}
}
}
@ -271,6 +299,18 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
pullDownPushHandler?.doPreProcess(this as? ISmartRefresh, popupPush)
}
private fun observeVolume() {
context?.applicationContext?.contentResolver?.registerContentObserver(
android.provider.Settings.System.CONTENT_URI,
true,
mVolumeObserver
)
}
private fun unObserveVolume() {
context?.applicationContext?.contentResolver?.unregisterContentObserver(mVolumeObserver)
}
override fun onFragmentPause() {
super.onFragmentPause()
DownloadManager.getInstance().removeObserver(mDataWatcher)
@ -434,6 +474,8 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
oldState: RefreshState,
newState: RefreshState
) {
(mContentFragment as? ISmartRefreshContent)?.onStateChanged(refreshLayout, oldState, newState)
val isAutoScrollToTwoLevel = oldState == RefreshState.None && newState == RefreshState.TwoLevel
val isDragToTwoLevel =
oldState == RefreshState.ReleaseToRefresh && newState == RefreshState.ReleaseToTwoLevel
@ -550,12 +592,9 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
when (newState) {
RefreshState.TwoLevel -> {
(mContentFragment as? ISmartRefreshContent)?.setScrollEnabled(false)
mBinding.refreshLayout.isDisableContent = false
}
RefreshState.None -> {
(mContentFragment as? ISmartRefreshContent)?.setScrollEnabled(true)
mShowTwoLevel = false
mIsDragging = false
twoLevelHeader.setFloorDuration(1000)
@ -739,6 +778,7 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
} else {
resumePullDownPushVideo()
}
observeVolume()
}
}
}
@ -752,6 +792,7 @@ class ToolbarWrapperFragment : LazyFragment(), ToolbarController, ISmartRefresh,
if (videoUrl.isNotEmpty()) {
ScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(videoUrl), currentPosition)
}
unObserveVolume()
}
}
}

View File

@ -18,6 +18,7 @@ import com.gh.gamecenter.common.utils.UploadImageUtils;
import com.gh.gamecenter.CropImageActivity;
import com.gh.gamecenter.R;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.common.view.cropbox.CropBoxStyle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -68,108 +69,104 @@ public class UserPortraitCropImageActivity extends CropImageActivity {
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.layout_menu_positive) {
final WaitingDialogFragment postDialog = WaitingDialogFragment.newInstance(getString(R.string.post_img));
postDialog.show(getSupportFragmentManager(), null);
final String path = getCacheDir() + File.separator + System.currentTimeMillis() + ".jpg";
Observable.create((ObservableOnSubscribe<String>) emitter -> {
boolean isSuccess = savePicture(path);
if (isSuccess) {
UploadImageUtils.INSTANCE.uploadImage(UploadImageUtils.UploadType.icon, path, new UploadImageUtils.OnUploadImageListener() {
@Override
public void onSuccess(@NotNull String imageUrl) {
emitter.onNext(imageUrl);
emitter.onComplete();
}
@Override
public void onError(@Nullable Throwable e) {
if (e != null) {
emitter.onError(e);
} else {
emitter.onError(new IllegalStateException("upload image error"));
}
}
@Override
public void onProgress(long total, long progress) {
int percent = (int) (100 * (progress / (float) total));
if (percent >= 100) percent = 99;
if (postDialog != null) {
postDialog.uploadWaitingHint("图片上传中 " + percent + "%");
}
}
});
}
}).subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Response<String>() {
@Override
public void onResponse(String url) {
try {
if (postDialog != null) postDialog.dismissAllowingStateLoss();
mBaseHandler.sendEmptyMessage(0);
String iconCount = sp.getString("updateIconCount", null);
long l = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
String time = format.format(new Date(l));
JSONObject jsonObject = new JSONObject();
jsonObject.put("time", time);
if (TextUtils.isEmpty(iconCount)) {
jsonObject.put("count", 1);
} else {
JSONObject json = new JSONObject(iconCount);
String lastTime = json.getString("time");
if (lastTime.equals(time)) {
jsonObject.put("count", json.getInt("count") + 1);
} else {
jsonObject.put("count", 1);
}
}
sp.edit().putString("updateIconCount", jsonObject.toString()).apply();
Intent data = new Intent();
data.putExtra(EntranceConsts.KEY_URL, url);
setResult(RESULT_OK, data);
finish();
} catch (Exception e) {
e.printStackTrace();
protected void saveImage() {
final WaitingDialogFragment postDialog = WaitingDialogFragment.newInstance(getString(R.string.post_img));
postDialog.show(getSupportFragmentManager(), null);
final String path = getCacheDir() + File.separator + System.currentTimeMillis() + ".jpg";
Observable.create((ObservableOnSubscribe<String>) emitter -> {
boolean isSuccess = savePicture(path);
if (isSuccess) {
UploadImageUtils.INSTANCE.uploadImage(UploadImageUtils.UploadType.icon, path, new UploadImageUtils.OnUploadImageListener() {
@Override
public void onSuccess(@NotNull String imageUrl) {
emitter.onNext(imageUrl);
emitter.onComplete();
}
}
@Override
public void onFailure(HttpException e) {
@Override
public void onError(@Nullable Throwable e) {
if (e != null) {
emitter.onError(e);
} else {
emitter.onError(new IllegalStateException("upload image error"));
}
}
@Override
public void onProgress(long total, long progress) {
int percent = (int) (100 * (progress / (float) total));
if (percent >= 100) percent = 99;
if (postDialog != null) {
postDialog.uploadWaitingHint("图片上传中 " + percent + "%");
}
}
});
}
}).subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Response<String>() {
@Override
public void onResponse(String url) {
try {
if (postDialog != null) postDialog.dismissAllowingStateLoss();
try {
if (e != null && e.code() == HttpURLConnection.HTTP_FORBIDDEN && e.response().errorBody() != null) {
JSONObject object = new JSONObject(e.response().errorBody().string());
String detail = object.getString("detail");
if ("too frequent".equals(detail)) {
mBaseHandler.sendEmptyMessage(2);
} else if ("INVALID PICTURE".equals(detail)) {
mBaseHandler.sendEmptyMessage(3);
} else {
mBaseHandler.sendEmptyMessage(1);
}
mBaseHandler.sendEmptyMessage(0);
String iconCount = sp.getString("updateIconCount", null);
long l = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
String time = format.format(new Date(l));
JSONObject jsonObject = new JSONObject();
jsonObject.put("time", time);
if (TextUtils.isEmpty(iconCount)) {
jsonObject.put("count", 1);
} else {
JSONObject json = new JSONObject(iconCount);
String lastTime = json.getString("time");
if (lastTime.equals(time)) {
jsonObject.put("count", json.getInt("count") + 1);
} else {
jsonObject.put("count", 1);
}
}
sp.edit().putString("updateIconCount", jsonObject.toString()).apply();
Intent data = new Intent();
data.putExtra(EntranceConsts.KEY_URL, url);
setResult(RESULT_OK, data);
finish();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(HttpException e) {
if (postDialog != null) postDialog.dismissAllowingStateLoss();
try {
if (e != null && e.code() == HttpURLConnection.HTTP_FORBIDDEN && e.response().errorBody() != null) {
JSONObject object = new JSONObject(e.response().errorBody().string());
String detail = object.getString("detail");
if ("too frequent".equals(detail)) {
mBaseHandler.sendEmptyMessage(2);
} else if ("INVALID PICTURE".equals(detail)) {
mBaseHandler.sendEmptyMessage(3);
} else {
mBaseHandler.sendEmptyMessage(1);
}
} catch (Exception e1) {
e1.printStackTrace();
} else {
mBaseHandler.sendEmptyMessage(1);
}
} catch (Exception e1) {
e1.printStackTrace();
mBaseHandler.sendEmptyMessage(1);
}
});
}
});
return false;
}
return super.onMenuItemClick(item);
}
// 用户头像压缩规则
@ -188,11 +185,11 @@ public class UserPortraitCropImageActivity extends CropImageActivity {
}
@NonNull
public static Intent getIntent(Context context, String picturePath, float cropRatio, String entrance) {
public static Intent getIntent(Context context, String picturePath, String entrance) {
Intent intent = new Intent(context, UserPortraitCropImageActivity.class);
intent.putExtra(EntranceConsts.KEY_PATH, picturePath);
intent.putExtra(EntranceConsts.KEY_ENTRANCE, entrance);
intent.putExtra(EntranceConsts.KEY_IMAGE_CROP_RATIO, cropRatio);
intent.putExtra(EntranceConsts.KEY_IMAGE_CROP_STYLE, CropBoxStyle.Circle.INSTANCE);
return intent;
}
}

View File

@ -11,6 +11,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import com.gh.common.provider.CropImageProviderImpl.Companion.IMAGE_TYPE_AVATAR
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
@ -93,23 +94,13 @@ class ChangeAvatarDialog : BaseDialogFragment() {
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
REQUEST_MEDIA_ICON -> {
val selectedPaths = Matisse.obtainResult(data) ?: return
val picturePath = PathUtils.getPath(requireContext(), selectedPaths[0])
Utils.log("picturePath = $picturePath")
// 上传头像
val intent = UserPortraitCropImageActivity.getIntent(
context,
picturePath, 1F, "我的光环(选择头像)"
)
startActivityForResult(intent, REQUEST_CROP_ICON)
}
REQUEST_CROP_ICON -> {
if (data.extras != null) {
val url = data.extras!!.getString(EntranceConsts.KEY_URL)
mUserViewModel?.changeUserInfo(url, UserViewModel.TYPE_PORTRAIT)
mUploadAvatar = true
}
}
ChooseDefaultAvatarDialog.REQUEST_CODE_DEFAULT_AVATAR -> {
val tag = requireArguments().getString(EntranceConsts.KEY_PARENT_TAG)
requireActivity().supportFragmentManager.findFragmentByTag(tag)
@ -145,7 +136,7 @@ class ChangeAvatarDialog : BaseDialogFragment() {
}
}
val intent = LocalMediaActivity.getIntent(requireContext(), ChooseType.IMAGE, 1, "头像选择")
val intent = LocalMediaActivity.getIntent(requireContext(), ChooseType.IMAGE, 1, "头像选择", IMAGE_TYPE_AVATAR)
startActivityForResult(intent, REQUEST_MEDIA_ICON)
}
@ -154,7 +145,6 @@ class ChangeAvatarDialog : BaseDialogFragment() {
}
companion object {
const val REQUEST_CROP_ICON = 12
const val REQUEST_MEDIA_ICON = 13
const val REQUEST_CODE_UPLOAD_AVATAR = 100

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="999dp" />
<solid android:color="#2496FF" />
</shape>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.183,5.778C11.51,5.511 12,5.744 12,6.165V8.909C12,9.461 12.448,9.909 13,9.909C13.552,9.909 14,9.461 14,8.909V6.165C14,4.056 11.549,2.895 9.917,4.23L8.756,5.181C8.328,5.53 8.265,6.16 8.615,6.588C8.965,7.015 9.595,7.078 10.022,6.728L11.183,5.778Z"
android:fillColor="#000000"/>
<path
android:pathData="M20.66,7C20.384,6.522 19.772,6.358 19.294,6.634C18.816,6.91 18.652,7.522 18.928,8C19.63,9.216 20,10.596 20,12C20,12.92 19.841,13.83 19.534,14.69C19.349,15.21 19.62,15.782 20.14,15.968C20.66,16.153 21.232,15.882 21.418,15.362C21.802,14.287 22,13.15 22,12C22,10.245 21.538,8.52 20.66,7Z"
android:fillColor="#000000"/>
<path
android:pathData="M17.796,10.447C17.653,9.914 17.104,9.597 16.571,9.74C16.037,9.883 15.721,10.431 15.864,10.965C15.967,11.35 16.012,11.748 15.998,12.143C15.978,12.695 16.409,13.159 16.961,13.179C17.513,13.198 17.976,12.767 17.996,12.215C18.017,11.622 17.951,11.026 17.796,10.447Z"
android:fillColor="#000000"/>
<path
android:pathData="M2.224,4.869C2.572,4.441 3.202,4.376 3.631,4.724L19.631,17.724C20.059,18.072 20.124,18.702 19.776,19.131C19.428,19.559 18.798,19.624 18.369,19.276L14,15.726V17.835C14,19.944 11.549,21.105 9.917,19.77L7.143,17.5H5C3.343,17.5 2,16.157 2,14.5V9.5C2,8.476 2.513,7.571 3.297,7.03L2.369,6.276C1.941,5.928 1.876,5.298 2.224,4.869ZM5.106,8.5L12,14.101V17.835C12,18.256 11.51,18.489 11.183,18.222L8.133,15.726C7.955,15.58 7.731,15.5 7.5,15.5H5C4.448,15.5 4,15.052 4,14.5V9.5C4,8.948 4.448,8.5 5,8.5H5.106Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M9.917,4.23C11.549,2.895 14,4.056 14,6.165V17.835C14,19.944 11.549,21.105 9.917,19.77L7.143,17.5H5C3.343,17.5 2,16.157 2,14.5V9.5C2,7.843 3.343,6.5 5,6.5H7.143L9.917,4.23ZM12,6.165C12,5.744 11.51,5.511 11.183,5.778L8.133,8.274C7.955,8.42 7.731,8.5 7.5,8.5H5C4.448,8.5 4,8.948 4,9.5V14.5C4,15.052 4.448,15.5 5,15.5H7.5C7.731,15.5 7.955,15.58 8.133,15.726L11.183,18.222C11.51,18.489 12,18.257 12,17.835V6.165Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M16.571,9.74C17.104,9.597 17.653,9.914 17.796,10.447C18.068,11.464 18.068,12.536 17.796,13.553C17.653,14.086 17.104,14.403 16.571,14.26C16.037,14.117 15.721,13.569 15.864,13.035C16.045,12.357 16.045,11.643 15.864,10.965C15.721,10.431 16.037,9.883 16.571,9.74Z"
android:fillColor="#000000"/>
<path
android:pathData="M20.66,7C20.384,6.522 19.772,6.358 19.294,6.634C18.816,6.91 18.652,7.522 18.928,8C19.63,9.216 20,10.596 20,12C20,13.404 19.63,14.784 18.928,16C18.652,16.478 18.816,17.09 19.294,17.366C19.772,17.642 20.384,17.478 20.66,17C21.538,15.48 22,13.755 22,12C22,10.245 21.538,8.52 20.66,7Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<group>
<clip-path
android:pathData="M0,0h16v16h-16z"/>
<path
android:pathData="M8,8m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
android:fillColor="#232323"/>
<path
android:pathData="M8,8m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
android:fillColor="#ffffff"
android:fillAlpha="0.1"/>
<path
android:pathData="M10,6L6,10M6,6L10,10"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M4,0L52,0A4,4 0,0 1,56 4L56,52A4,4 0,0 1,52 56L4,56A4,4 0,0 1,0 52L0,4A4,4 0,0 1,4 0z"
android:fillColor="#ffffff"
android:fillAlpha="0.1"/>
<path
android:pathData="M20.5,17C20.5,16.448 20.948,16 21.5,16H34.5C35.052,16 35.5,16.448 35.5,17V23.086L34.268,21.854C33.292,20.877 31.708,20.877 30.732,21.854L28.232,24.354C28.139,24.447 28.011,24.5 27.879,24.5H26.621C25.958,24.5 25.322,24.763 24.854,25.232L21.149,28.937C20.77,28.795 20.5,28.429 20.5,28V17ZM21.501,31H21.5H21.501ZM21.501,31H34.5C36.157,31 37.5,29.657 37.5,28V25.501C37.5,25.5 37.5,25.5 37.5,25.499V17C37.5,15.343 36.157,14 34.5,14H21.5C19.843,14 18.5,15.343 18.5,17V28C18.5,29.656 19.843,30.999 21.499,31M35.5,25.914V28C35.5,28.552 35.052,29 34.5,29H23.914L26.268,26.646C26.361,26.553 26.489,26.5 26.621,26.5H27.879C28.542,26.5 29.178,26.237 29.646,25.768L32.146,23.268C32.342,23.073 32.658,23.073 32.854,23.268L35.5,25.914ZM24.75,19.5C24.336,19.5 24,19.836 24,20.25C24,20.664 24.336,21 24.75,21C25.164,21 25.5,20.664 25.5,20.25C25.5,19.836 25.164,19.5 24.75,19.5ZM22,20.25C22,18.731 23.231,17.5 24.75,17.5C26.269,17.5 27.5,18.731 27.5,20.25C27.5,21.769 26.269,23 24.75,23C23.231,23 22,21.769 22,20.25Z"
android:fillColor="#ffffff"
android:fillAlpha="0.8"
android:fillType="evenOdd"/>
<path
android:pathData="M12.601,39.514V40.315C12.925,40.639 13.744,41.584 13.933,41.827L13.51,42.358C13.339,42.07 12.943,41.512 12.601,41.071V45.139H11.962V40.945C11.611,41.935 11.161,42.898 10.684,43.501C10.603,43.321 10.414,43.051 10.297,42.889C10.936,42.124 11.557,40.729 11.881,39.514H10.513V38.884H11.962V36.967H12.601V38.884H13.888V39.514H12.601ZM14.914,43.96H17.65V42.412H14.914V43.96ZM17.65,38.128H14.914V39.631H17.65V38.128ZM14.914,40.243V41.791H17.65V40.243H14.914ZM14.275,37.498H18.307V45.085H17.65V44.581H14.914V45.085H14.275V37.498ZM24.544,40.513H25.975V38.182H24.544V40.324V40.513ZM21.052,40.513H22.312V38.182H21.052V40.315V40.513ZM27.595,40.513V41.161H26.632V44.374C26.632,44.761 26.542,44.95 26.299,45.067C26.056,45.175 25.651,45.193 24.994,45.193C24.958,45.013 24.859,44.725 24.778,44.554C25.246,44.572 25.678,44.572 25.795,44.563C25.93,44.554 25.975,44.509 25.975,44.374V41.161H24.526C24.472,42.493 24.238,44.086 23.491,45.211C23.392,45.076 23.122,44.833 22.987,44.752C23.662,43.717 23.833,42.349 23.878,41.161H22.96V44.338C22.96,44.707 22.87,44.896 22.636,45.004C22.384,45.112 21.997,45.121 21.376,45.121C21.349,44.95 21.241,44.68 21.16,44.518C21.601,44.527 22.015,44.527 22.141,44.518C22.267,44.518 22.312,44.473 22.312,44.338V41.161H21.034C20.971,42.502 20.719,44.104 19.882,45.247C19.783,45.103 19.531,44.851 19.396,44.779C20.143,43.735 20.341,42.358 20.395,41.161H19.405V40.513H20.413V40.315V37.561H22.96V40.513H23.896V40.324V37.561H26.632V40.513H27.595ZM34.093,40.693H33.175C33.004,42.106 32.572,43.141 31.06,43.726C30.979,43.564 30.799,43.321 30.655,43.195C32.005,42.718 32.365,41.863 32.509,40.693H30.898V40.108H33.418V38.794H32.095C31.915,39.136 31.726,39.451 31.51,39.703C31.393,39.604 31.105,39.433 30.952,39.361C31.429,38.83 31.807,38.029 32.023,37.237L32.662,37.381C32.581,37.66 32.482,37.939 32.365,38.209H33.418V37.003H34.084V38.209H36.154V38.794H34.084V40.108H36.523V40.693H34.75V42.772C34.75,42.988 34.786,43.024 35.011,43.024H35.731C35.938,43.024 35.983,42.889 36.001,41.962C36.145,42.07 36.415,42.178 36.604,42.223C36.532,43.357 36.352,43.645 35.794,43.645H34.921C34.246,43.645 34.093,43.438 34.093,42.772V40.693ZM30.511,38.722L29.962,39.136C29.701,38.704 29.098,38.065 28.567,37.633L29.08,37.273C29.611,37.687 30.232,38.29 30.511,38.722ZM30.277,40.396V43.573C30.52,43.609 30.727,43.825 31.15,44.068C31.708,44.401 32.446,44.455 33.4,44.455C34.426,44.455 35.776,44.392 36.667,44.293C36.586,44.473 36.478,44.797 36.469,44.986C35.776,45.031 34.264,45.085 33.391,45.085C32.347,45.085 31.618,44.986 31.024,44.644C30.673,44.428 30.403,44.176 30.205,44.176C29.872,44.176 29.386,44.626 28.882,45.184L28.441,44.617C28.837,44.239 29.251,43.915 29.629,43.735V41.026H28.522V40.396H30.277ZM40.303,41.413L39.268,41.755V44.365C39.268,44.743 39.187,44.923 38.953,45.031C38.737,45.139 38.395,45.157 37.819,45.157C37.792,44.977 37.702,44.707 37.621,44.518C37.999,44.536 38.359,44.536 38.458,44.527C38.575,44.527 38.62,44.491 38.62,44.365V41.962C38.224,42.097 37.855,42.214 37.522,42.322L37.351,41.674C37.702,41.575 38.143,41.44 38.62,41.296V39.388H37.441V38.767H38.62V36.976H39.268V38.767H40.33V39.388H39.268V41.098L40.213,40.801L40.303,41.413ZM43.237,42.187V43.114H45.586V43.717H43.237V45.211H42.58V43.717H40.303V43.114H42.58V42.187H40.762V41.593H42.58V40.801H43.237V41.593H44.938V42.187H43.237ZM44.227,38.056H41.788C42.085,38.524 42.481,38.938 42.949,39.289C43.462,38.938 43.903,38.524 44.227,38.056ZM44.749,37.426L45.154,37.651C44.767,38.434 44.173,39.1 43.462,39.64C44.119,40.045 44.893,40.351 45.73,40.531C45.595,40.666 45.415,40.918 45.316,41.089C44.434,40.864 43.624,40.486 42.931,40.009C42.193,40.486 41.365,40.846 40.537,41.08C40.465,40.927 40.312,40.675 40.186,40.54C40.942,40.351 41.734,40.045 42.436,39.622C41.914,39.172 41.473,38.65 41.149,38.056H40.564V37.453H44.641L44.749,37.426Z"
android:fillColor="#ffffff"
android:fillAlpha="0.8"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="999dp" />
<solid android:color="#2496FF" />
</shape>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="2dp"
android:color="@color/white" />
<corners android:radius="2dp" />
</shape>

View File

@ -8,48 +8,41 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="#1A1A1A">
<com.gh.gamecenter.common.view.CropImageCustom
android:id="@+id/cropImageIv"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:horizontalPadding="0dp"
app:horizontalPadding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="68dp"
android:background="@color/black_alpha_40"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/cancelTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_marginLeft="16dp"
android:layout_marginBottom="35dp"
android:layout_marginBottom="12dp"
android:text="取消"
android:textColor="@color/white"
android:textSize="13sp"
android:textSize="12sp"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/nextTv"
android:layout_width="60dp"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="28dp"
android:background="@drawable/bg_notification_open_btn_style_2"
android:layout_marginBottom="12dp"
android:background="@drawable/bg_image_clip_submit"
android:gravity="center"
android:text="下一步"
android:text="@string/confirm"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"

View File

@ -13,27 +13,39 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/status_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
<com.gh.gamecenter.common.view.CropImageCustom
android:id="@+id/cropimage_custom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/status_bar"
android:orientation="vertical">
android:background="@color/ui_background_fixed_dark" />
<include layout="@layout/reuse_toolbar" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="52dp"
android:layout_alignParentBottom="true">
<com.gh.gamecenter.common.view.CropImageCustom
android:id="@+id/cropimage_custom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black" />
</LinearLayout>
<TextView
android:id="@+id/tv_cancel"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_marginStart="16dp"
android:gravity="center"
android:text="@string/cancel"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_submit"
style="@style/BtnSmallStyle"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_image_clip_submit"
android:text="@string/confirm"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.gh.gamecenter.common.view.MaterializedRelativeLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fcv_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -164,6 +164,19 @@
app:lottie_loop="true"
app:lottie_repeatMode="restart" />
<ImageView
android:id="@+id/muteIv"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="24dp"
android:padding="4dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_basic_offsound"
app:tint="@color/text_instance" />
<FrameLayout
android:id="@+id/headerBackground"
android:layout_width="0dp"

View File

@ -169,6 +169,19 @@
app:lottie_loop="true"
app:lottie_repeatMode="restart" />
<ImageView
android:id="@+id/muteIv"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="24dp"
android:padding="4dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_basic_offsound"
app:tint="@color/text_instance" />
<View
android:id="@+id/headerBackground"
android:layout_width="0dp"

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1A1A1A">
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/status_bar_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.gh.gamecenter.common.view.CropImageCustom
android:id="@+id/crop_image_custom"
android:layout_width="0dp"
android:layout_height="0dp"
app:horizontalPadding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/v_bottom_background"
android:layout_width="0dp"
android:layout_height="160dp"
android:background="#232323"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.gh.gamecenter.common.view.DividerView
android:id="@+id/v_line"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="51dp"
android:background="#333333"
app:divider_size="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tv_cancel"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_marginStart="16dp"
android:gravity="center"
android:text="@string/cancel"
android:textColor="#FFFFFF"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/v_line" />
<TextView
android:id="@+id/tv_submit"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_marginEnd="16dp"
android:background="@drawable/shape_video_cover_edit_submit"
android:gravity="center"
android:text="@string/confirm"
android:textColor="#ffffff"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/v_line" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_take_photo"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="12dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_video_cover_edit_take_photo"
app:layout_constraintBottom_toBottomOf="@id/v_line"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_preview_border"
android:layout_width="56dp"
android:layout_height="56dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/iv_take_photo"
app:layout_constraintEnd_toEndOf="@id/iv_take_photo"
app:layout_constraintStart_toStartOf="@id/iv_take_photo"
app:layout_constraintTop_toTopOf="@id/iv_take_photo" />
<ImageView
android:id="@+id/iv_remove"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="-6dp"
android:layout_marginEnd="-6dp"
android:src="@drawable/ic_video_cover_edit_remove"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/iv_take_photo"
app:layout_constraintTop_toTopOf="@id/iv_take_photo"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_images"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@id/iv_take_photo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_take_photo"
app:layout_constraintTop_toTopOf="@id/iv_take_photo"
tools:background="#1AFFFFFF" />
<TextView
android:id="@+id/tv_bottom_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="12dp"
android:text="@string/select_from_album_or_select_video_screenshot"
android:textColor="#99FFFFFF"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@id/iv_take_photo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,6 +10,7 @@
android:id="@+id/cv_banner_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardBackgroundColor="@color/transparent"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:layout_constraintDimensionRatio="41:11"

View File

@ -2,38 +2,49 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="64dp">
android:layout_height="56dp">
<androidx.cardview.widget.CardView
android:id="@+id/preview_container"
android:layout_width="match_parent"
android:layout_height="60dp"
app:cardCornerRadius="3dp"
app:cardElevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/text_28282E"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.cardview.widget.CardView>
<View
android:id="@+id/preview_border"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/video_poster_preview_border"
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/preview"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/text_28282E"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/preview_border_out"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/preview_border_inner"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/g_preview_border"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="preview_border_out,preview_border_inner" />
<com.gh.gamecenter.video.poster.PreviewMaskView
android:id="@+id/v_cover"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -593,6 +593,7 @@
<string name="dialog_vgame_installed_launch_button">啟動遊戲</string>
<string name="dialog_vgame_installed_launch_desc_1">加載完成,馬上開始遊戲吧</string>
<string name="dialog_vgame_installed_launch_desc_2">%1$d款遊戲加載完成馬上開始遊戲吧</string>
<string name="select_from_album_or_select_video_screenshot">從相簿選擇或選擇影片截圖</string>
<string name="reverse_success_without_reminder_tips">遊戲上線後,您將在訊息中心收到通知</string>
<string name="reverse_success_with_reminder_tips">遊戲上線後,您將在訊息中心收到通知,及以下方式提醒</string>
<string name="sms_reminder">簡訊提醒</string>

View File

@ -593,6 +593,7 @@
<string name="dialog_vgame_installed_launch_button">启动游戏</string>
<string name="dialog_vgame_installed_launch_desc_1">加载完成,马上开始游戏吧</string>
<string name="dialog_vgame_installed_launch_desc_2">%1$d款游戏加载完成马上开始游戏吧</string>
<string name="select_from_album_or_select_video_screenshot">从相册选择或选择视频截图</string>
<string name="reverse_success_without_reminder_tips">游戏上线后,您将在消息中心收到通知</string>
<string name="reverse_success_with_reminder_tips">游戏上线后,您将在消息中心收到通知,及以下方式提醒</string>
<string name="sms_reminder">短信提醒</string>

View File

@ -41,13 +41,18 @@ class LocalMediaActivity : BaseActivity() {
context: Context,
chooseType: ChooseType,
maxChooseCount: Int = 1,
entrance: String
entrance: String,
imageType: Int = 0
): Intent {
return Intent(context, LocalMediaActivity::class.java).apply {
putExtra(EntranceConsts.KEY_TYPE, chooseType.value)
putExtra(EntranceConsts.KEY_CHOOSE_MAX_COUNT, maxChooseCount)
putExtra(EntranceConsts.KEY_QUICK_CHOOSE, maxChooseCount == 1)
putExtra(
EntranceConsts.KEY_QUICK_CHOOSE,
if (chooseType == ChooseType.VIDEO) false else maxChooseCount == 1
)
putExtra(EntranceConsts.KEY_ENTRANCE, entrance)
putExtra(EntranceConsts.KEY_IMAGE_TYPE, imageType)
}
}
}

View File

@ -93,7 +93,7 @@ class LocalMediaAdapter(
holder.binding.checkImageView.visibility = View.GONE
holder.itemView.setOnClickListener {
viewModel.addSelection(item)
viewModel.setSelection(item)
viewModel.postSelectedResult()
}
} else {

View File

@ -14,6 +14,7 @@ import android.widget.PopupWindow
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
@ -23,11 +24,13 @@ import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.common.utils.enlargeTouchArea
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.GridSpacingItemDecoration
import com.gh.gamecenter.core.provider.ICropImageProvider
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.MimeType
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.selector.databinding.FragmentLocalMediaBinding
import com.therouter.TheRouter
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.Item
@ -60,6 +63,7 @@ class LocalMediaFragment : BaseFragment<Any>(), AlbumMediaCollection.AlbumMediaC
private val albumCollection = AlbumCollection()
private var isFirstAlbumLoad = true
private var imageType = 0
override fun getLayoutId(): Int = 0
@ -77,12 +81,15 @@ class LocalMediaFragment : BaseFragment<Any>(), AlbumMediaCollection.AlbumMediaC
isQuickChoose = savedInstanceState?.getBoolean(EntranceConsts.KEY_QUICK_CHOOSE, false)
?: arguments?.getBoolean(EntranceConsts.KEY_QUICK_CHOOSE, false)
?: false
imageType = savedInstanceState?.getInt(EntranceConsts.KEY_IMAGE_TYPE, 0)
?: arguments?.getInt(EntranceConsts.KEY_IMAGE_TYPE, 0) ?: 0
viewModel = viewModelProviderFromParent(LocalMediaViewModel.Factory(maxChooseCount))
// 页面恢复时,恢复选中的数据
if (savedInstanceState != null) {
val savedSelectedItem: ArrayList<Item>? = savedInstanceState.getParcelableArrayList(EntranceConsts.KEY_VIDEO_LIST)
val savedSelectedItem: ArrayList<Item>? =
savedInstanceState.getParcelableArrayList(EntranceConsts.KEY_VIDEO_LIST)
if (savedSelectedItem != null) {
for (selectedItem in savedSelectedItem) {
viewModel.addSelection(selectedItem)
@ -164,14 +171,27 @@ class LocalMediaFragment : BaseFragment<Any>(), AlbumMediaCollection.AlbumMediaC
)
}
intent.putExtra(LocalVideoEntity::class.java.name, localVideoList)
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
} else {
val data = viewModel.selectedItemList.map { it.contentUri }.toList()
val path = data.map { PathUtils.getPath(requireContext(), it) }.toList()
intent.putParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION, ArrayList<Uri>(data))
intent.putStringArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH, ArrayList<String>(path))
val imageProvider = TheRouter.get(ICropImageProvider::class.java)
val toIntent = imageProvider?.getCropImageIntent(data, imageType, mEntrance, requireContext())
if (toIntent != null) {
// 需要裁剪
startActivityForResult(toIntent, REQUEST_CROP_ICON)
} else {
intent.putParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION, ArrayList(data))
intent.putStringArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH, ArrayList(path))
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
}
}
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
}
}
@ -196,12 +216,21 @@ class LocalMediaFragment : BaseFragment<Any>(), AlbumMediaCollection.AlbumMediaC
binding.pieceMediaControl.previewTv.isEnabled = isNotEmpty
binding.pieceMediaControl.confirmTv.isEnabled = isNotEmpty
if (isNotEmpty) {
binding.pieceMediaControl.previewTv.setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(requireContext()))
binding.pieceMediaControl.previewTv.setTextColor(
com.gh.gamecenter.common.R.color.text_aw_primary.toColor(
requireContext()
)
)
binding.pieceMediaControl.previewTv.alpha = 1F
binding.pieceMediaControl.confirmTv.alpha = 1F
binding.pieceMediaControl.confirmTv.text = "${getString(com.gh.gamecenter.common.R.string.confirm)}(${it})"
binding.pieceMediaControl.confirmTv.text =
"${getString(com.gh.gamecenter.common.R.string.confirm)}(${it})"
} else {
binding.pieceMediaControl.previewTv.setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(requireContext()))
binding.pieceMediaControl.previewTv.setTextColor(
com.gh.gamecenter.common.R.color.text_aw_primary.toColor(
requireContext()
)
)
binding.pieceMediaControl.previewTv.alpha = 0.6F
binding.pieceMediaControl.confirmTv.alpha = 0.6F
binding.pieceMediaControl.confirmTv.text = getString(com.gh.gamecenter.common.R.string.confirm)
@ -333,4 +362,25 @@ class LocalMediaFragment : BaseFragment<Any>(), AlbumMediaCollection.AlbumMediaC
albumMediaCollection?.onDestroy()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
REQUEST_CROP_ICON -> {
// 裁剪成功
if (data != null) {
val intent = Intent()
intent.putExtras(data)
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
}
}
}
}
}
companion object {
private const val REQUEST_CROP_ICON = 13
}
}

View File

@ -61,6 +61,7 @@ import com.github.piasy.biv.view.BigImageView
import com.github.piasy.biv.view.FrescoImageViewFactory
import com.lightgame.listeners.OnBackPressedListener
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView.CURRENT_STATE_PLAYING
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.Item
import java.io.File
@ -171,7 +172,12 @@ class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnB
parentViewModel?.removeSelection(currentDisplayingItem!!)
} else {
if (parentViewModel?.addSelection(currentDisplayingItem!!) == false) {
ToastUtils.showToast("至多选择${parentViewModel?.maxSelectionSize}张图片")
val toastText = if (currentDisplayingItem?.isVideo == true) {
ToastUtils.showToast("至多选择${parentViewModel?.maxSelectionSize}条视频")
} else {
ToastUtils.showToast("至多选择${parentViewModel?.maxSelectionSize}张图片")
}
}
}
@ -211,7 +217,11 @@ class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnB
binding.mediaControl.confirmTv.text = "确定(${list.size})"
binding.mediaControl.confirmTv.alpha = 1f
} else {
binding.mediaControl.previewTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
binding.mediaControl.previewTv.setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(
requireContext()
)
)
binding.mediaControl.confirmTv.text = "确定"
binding.mediaControl.confirmTv.alpha = 0.6f
}
@ -225,7 +235,8 @@ class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnB
}
if (previewItem?.isVideo != true
&& currentDisplayingItem?.id == previewItem?.id ) {
&& currentDisplayingItem?.id == previewItem?.id
) {
initEnterAnimation()
}
@ -808,7 +819,9 @@ class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnB
videoViewMap.put(position, binding.videoView)
binding.videoView.updateThumb(item.contentUri.toString())
binding.videoView.setAlOnClickListener { toggleToolbarAndBottomContainer() }
binding.videoView.setAlOnClickListener {
binding.videoView.startButton.performClick()
}
setDragListener(binding.videoView)
GSYVideoOptionBuilder()
.setIsTouchWiget(false)
@ -819,7 +832,12 @@ class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnB
.setReleaseWhenLossAudio(true)
.setLooping(false)
.setShowFullAnimation(false)
.setGSYStateUiListener {
toggleToolbarAndBottomContainer(it != CURRENT_STATE_PLAYING)
}
.build(binding.videoView)
} else {
if (useEnterAndExitAnimation) {
containerMap.put(position, view)
@ -829,7 +847,9 @@ class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnB
}
setDragListener(binding.iv)
binding.iv.setOnClickListener { toggleToolbarAndBottomContainer() }
binding.iv.setOnClickListener {
toggleToolbarAndBottomContainer(bottomContainer.visibility != View.VISIBLE)
}
binding.iv.setImageLoaderCallback(object : SimpleImageLoader() {
override fun onSuccess(image: File) {
val ssiv = binding.iv.ssiv
@ -868,13 +888,13 @@ class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnB
private fun isFadeOnly() =
currentDisplayingItem?.isVideo == true || originLeftMap?.get(currentDisplayingItem?.uri?.toString()) == null
private fun toggleToolbarAndBottomContainer() {
if (bottomContainer.visibility == View.VISIBLE) {
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
} else {
private fun toggleToolbarAndBottomContainer(showController: Boolean) {
if (showController) {
bottomContainer.visibility = View.VISIBLE
toolbarContainer.visibility = View.VISIBLE
} else {
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
}
}

View File

@ -48,6 +48,10 @@ class LocalMediaRepository {
_selectedItemListFlow.tryEmit(newList)
}
fun setSelection(item: Item) {
_selectedItemListFlow.tryEmit(arrayListOf(item))
}
fun closeCursor() {
albumCursor?.close()
}

View File

@ -70,6 +70,10 @@ class LocalMediaViewModel(val maxSelectionSize: Int) : ViewModel() {
return true
}
fun setSelection(item: Item) {
localMediaRepo.setSelection(item)
}
fun removeSelection(item: Item) {
localMediaRepo.removeSelection(item)
}

View File

@ -2,9 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="52dp"
android:background="@color/ui_surface_fixed_dark"
android:paddingStart="16dp"
android:paddingEnd="16dp">
android:background="@color/ui_surface_fixed_dark">
<TextView
android:id="@+id/previewTv"
@ -14,6 +12,7 @@
android:enabled="false"
android:gravity="center"
android:text="预览"
android:layout_marginHorizontal="16dp"
android:textColor="@color/text_aw_primary"
android:textSize="12sp" />
@ -24,6 +23,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:alpha="0.6"
android:layout_marginHorizontal="16dp"
android:background="@drawable/button_blue_oval"
android:enabled="false"
android:gravity="center"

View File

@ -475,4 +475,6 @@ public class Constants {
public static final String TOOL_MAP_PACKAGE_NAME = "com.gh.toolmap";// 光环工具服务APP包名
public static final String SP_VIDEO_COVER_CACHE = "sp_video_cover_cache";
}

View File

@ -209,6 +209,7 @@ public class EntranceConsts {
public static final String KEY_OPEN_KEYBOARD = "openKeyboard";
public static final String KEY_PATH_VIDEO = "pathVideo";
public static final String KEY_VIDEO_ID = "videoId";
public static final String KEY_VIDEO_ENTITY = "key_video_entity";
public static final String KEY_DIRECT_COMMENT = "directComment";
public static final String KEY_SORT = "sort";
public static final String KEY_AMWAY = "amway";
@ -219,6 +220,8 @@ public class EntranceConsts {
public static final String KEY_COLLECTION_NAME = "collectionName";
public static final String KEY_NAVIGATION_TITLE = "navigationTitle";
public static final String KEY_IMAGE_CROP_STYLE = "imageCropStyle";
public static final String KEY_IMAGE_TYPE = "imageType";
public static final String KEY_IMAGE_CROP_RATIO = "imageCropRatio";
public static final String KEY_OPEN_VIDEO_STREAMING = "open_video_streaming";
public static final String KEY_REFERER = "referer";

View File

@ -14,6 +14,7 @@ object RouteConsts {
const val forumVideoDetailActivity = "/app/forumVideoDetailActivity"
const val libaoDetailActivity = "/app/libaoDetailActivity"
const val fullScreenVideoActivity = "/app/FullScreenVideoActivity"
const val videoCoverEditActivity = "/app/videoCoverEditActivity"
const val gameDetailActivity = "/activity/game_detail"
const val userHomeActivity = "/activity/user_home"

View File

@ -10,6 +10,8 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.gh.gamecenter.common.R;
import com.gh.gamecenter.common.view.cropbox.CropBoxStyle;
import com.gh.gamecenter.common.view.cropbox.CropBoxView;
import com.gh.gamecenter.core.utils.DisplayUtils;
import java.io.BufferedOutputStream;
@ -19,9 +21,9 @@ import java.io.FileOutputStream;
public class CropImageCustom extends RelativeLayout {
private CropImageZoomView mZoomImageView;
private CropImageBorderView mClipImageView;
private CropBoxView mClipImageView;
private int mHorizontalPadding = 20;
private int mHorizontalPadding = 16;
private float mRatio = 1F; // 裁剪比例
@ -29,7 +31,7 @@ public class CropImageCustom extends RelativeLayout {
super(context, attrs);
mZoomImageView = new CropImageZoomView(context);
mClipImageView = new CropImageBorderView(context);
mClipImageView = new CropBoxView(context);
android.view.ViewGroup.LayoutParams lp = new LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
@ -47,13 +49,21 @@ public class CropImageCustom extends RelativeLayout {
ta.recycle();
mZoomImageView.setHorizontalPadding(mHorizontalPadding, mRatio);
mClipImageView.setHorizontalPadding(mHorizontalPadding, mRatio);
mClipImageView.setHorizontalPaddingBoxStyle(mHorizontalPadding, new CropBoxStyle.Rectangle(mRatio));
mZoomImageView.setOnDragStateChangeListener(isDragging -> {
mClipImageView.changeDragState(isDragging);
});
}
public void setCropRatio(float ratio) {
this.mRatio = ratio;
public void setCropBoxStyle(CropBoxStyle boxStyle) {
if (boxStyle instanceof CropBoxStyle.Circle) {
mRatio = 1F;
}
if (boxStyle instanceof CropBoxStyle.Rectangle) {
mRatio = ((CropBoxStyle.Rectangle) boxStyle).getRadio();
}
mZoomImageView.setHorizontalPadding(mHorizontalPadding, mRatio);
mClipImageView.setHorizontalPadding(mHorizontalPadding, mRatio);
mClipImageView.setHorizontalPaddingBoxStyle(mHorizontalPadding, boxStyle);
}
public void addAssistView(View view) {
@ -99,4 +109,16 @@ public class CropImageCustom extends RelativeLayout {
mZoomImageView.setImageBitmap(bitmap);
}
public void reset() {
mZoomImageView.reset();
}
public CropImageZoomView.TransformationValues getTransformationValues() {
return mZoomImageView.getTransformationValues();
}
public void setTransformationValues(CropImageZoomView.TransformationValues values) {
mZoomImageView.setTransformationValues(values);
}
}

View File

@ -14,6 +14,7 @@ import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import androidx.appcompat.widget.AppCompatImageView;
@ -30,11 +31,13 @@ public class CropImageZoomView extends AppCompatImageView implements
*/
private final float[] matrixValues = new float[9];
private final Matrix mScaleMatrix = new Matrix();
private TransformationValues cacheValues;
/**
* 初始化时的缩放比例如果图片宽或高大于屏幕此值将小于0
*/
private float initScale = 1.0f;
private boolean once = true;
/**
* 缩放的手势检测
*/
@ -65,13 +68,17 @@ public class CropImageZoomView extends AppCompatImageView implements
private int mWidth;
private boolean isDragging = false;
private int touchSlop;
public CropImageZoomView(Context context) {
this(context, null);
}
public CropImageZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setScaleType(ScaleType.MATRIX);
mGestureDetector = new GestureDetector(context,
new SimpleOnGestureListener() {
@ -109,6 +116,20 @@ public class CropImageZoomView extends AppCompatImageView implements
return matrixValues[Matrix.MSCALE_X];
}
public TransformationValues getTransformationValues() {
mScaleMatrix.getValues(matrixValues);
return new TransformationValues(
matrixValues[Matrix.MSCALE_X],
matrixValues[Matrix.MSCALE_Y],
matrixValues[Matrix.MTRANS_X],
matrixValues[Matrix.MTRANS_Y]
);
}
public void setTransformationValues(TransformationValues values) {
cacheValues = values;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = getScale();
@ -252,6 +273,18 @@ public class CropImageZoomView extends AppCompatImageView implements
checkBorder();
setImageMatrix(mScaleMatrix);
}
boolean isMoving = false;
if (Math.abs(dx) > mTouchSlop) {
isMoving = true;
}
if (Math.abs(dy) > mTouchSlop) {
isMoving = true;
}
if (isMoving && stateListener != null && !isDragging) {
isDragging = true;
stateListener.onStateChanged(true);
}
}
mLastX = x;
mLastY = y;
@ -260,6 +293,10 @@ public class CropImageZoomView extends AppCompatImageView implements
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
lastPointerCount = 0;
if (stateListener != null && isDragging) {
isDragging = false;
stateListener.onStateChanged(false);
}
break;
}
@ -333,9 +370,17 @@ public class CropImageZoomView extends AppCompatImageView implements
initScale = scale;
SCALE_MID = initScale * 2;
SCALE_MAX = initScale * 4;
mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);// 平移至屏幕中心
mScaleMatrix.postScale(scale, scale, getWidth() / 2,
getHeight() / 2);// 设置缩放比例
// 如果有缓存,直接显示缓存
if (cacheValues != null) {
mScaleMatrix.postScale(cacheValues.scaleX, cacheValues.scaleY);
mScaleMatrix.postTranslate(cacheValues.translateX, cacheValues.translateY);
cacheValues = null;
} else {
mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);// 平移至屏幕中心
mScaleMatrix.postScale(scale, scale, getWidth() / 2,
getHeight() / 2);// 设置缩放比例
}
// 图片移动至屏幕中心
setImageMatrix(mScaleMatrix);
once = false;
@ -365,6 +410,11 @@ public class CropImageZoomView extends AppCompatImageView implements
this.mRatio = ratio;
}
public void reset() {
once = true;
mScaleMatrix.reset();
}
/**
* 自动缩放的任务
*
@ -424,4 +474,28 @@ public class CropImageZoomView extends AppCompatImageView implements
}
}
private OnDragStateListener stateListener;
interface OnDragStateListener {
void onStateChanged(boolean isDragging);
}
void setOnDragStateChangeListener(OnDragStateListener listener) {
stateListener = listener;
}
public static class TransformationValues {
public final float scaleX;
public final float scaleY;
public final float translateX;
public final float translateY;
public TransformationValues(float scaleX, float scaleY, float translateX, float translateY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
this.translateX = translateX;
this.translateY = translateY;
}
}
}

View File

@ -0,0 +1,43 @@
package com.gh.gamecenter.common.view
import android.content.Context
import android.util.AttributeSet
import android.view.View
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.utils.dip2px
class DividerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {
private var size = DIVIDER_SIZE_THIN
init {
initAttrs(context, attrs)
}
private fun initAttrs(context: Context, attrs: AttributeSet?) {
attrs ?: return
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DividerView)
size = typedArray.getInt(R.styleable.DividerView_divider_size, DIVIDER_SIZE_REGULAR)
typedArray.recycle()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val height = when (size) {
DIVIDER_SIZE_THIN -> 1
DIVIDER_SIZE_MEDIUM -> 0.5F.dip2px()
else -> 1F.dip2px()
}
setMeasuredDimension(widthMeasureSpec, height)
}
companion object {
private const val DIVIDER_SIZE_THIN = 0
private const val DIVIDER_SIZE_MEDIUM = 1
private const val DIVIDER_SIZE_REGULAR = 12
}
}

View File

@ -0,0 +1,102 @@
package com.gh.gamecenter.common.view.cropbox
import android.animation.ValueAnimator
import android.graphics.Color
import android.os.Handler
import android.os.Looper
import android.view.animation.AccelerateDecelerateInterpolator
class CropBoxAnimator(private val block: (Int) -> Unit) {
private var currentState: AnimatorState = AnimatorState.Default
private val handler = Handler(Looper.getMainLooper())
private val valueAnimator by lazy {
val startColor = Color.parseColor("#CC000000")
val endColor = Color.parseColor("#33000000")
ValueAnimator.ofArgb(startColor, endColor)
.setDuration(ANIMATOR_DURATION)
.apply {
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { animator ->
val animatedColor = animator.animatedValue as Int
block(animatedColor)
val time = animator.currentPlayTime
if (time >= ANIMATOR_DURATION) {
if (currentState is AnimatorState.Playing) {
currentState = AnimatorState.Dragging
}
if (currentState is AnimatorState.Reversing) {
currentState = AnimatorState.Default
}
}
}
}
}
private val task = Runnable {
// 延时结束,执行反向渐变动画
currentState = AnimatorState.Reversing
valueAnimator.reverse()
}
fun changeDragState(isDragging: Boolean) {
when (currentState) {
is AnimatorState.Default -> {
// 执行正向渐变动画
if (isDragging) {
currentState = AnimatorState.Playing
valueAnimator.start()
}
}
is AnimatorState.Playing -> {
if (!isDragging) {
currentState = AnimatorState.Delay
handler.removeCallbacksAndMessages(null)
handler.postDelayed(task, DELAY_DURATION)
}
}
is AnimatorState.Dragging -> {
if (!isDragging) {
currentState = AnimatorState.Delay
handler.removeCallbacksAndMessages(null)
handler.postDelayed(task, DELAY_DURATION)
}
}
is AnimatorState.Delay -> {
if (isDragging) {
currentState = AnimatorState.Dragging
handler.removeCallbacksAndMessages(null)
}
}
is AnimatorState.Reversing -> {
if (isDragging) {
currentState = AnimatorState.Playing
valueAnimator.reverse()
}
}
}
}
companion object {
private const val ANIMATOR_DURATION = 200L
private const val DELAY_DURATION = 500L
}
sealed class AnimatorState {
object Default : AnimatorState()
object Playing : AnimatorState()
object Dragging : AnimatorState()
object Delay : AnimatorState()
object Reversing : AnimatorState()
}
}

View File

@ -0,0 +1,40 @@
package com.gh.gamecenter.common.view.cropbox
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
interface CropBoxDrawer {
fun onDraw(rectF: RectF, canvas: Canvas, paint: Paint)
fun onDrawDecoration(rectF: RectF, canvas: Canvas, paint: Paint)
class CircleDrawer : CropBoxDrawer {
override fun onDraw(rectF: RectF, canvas: Canvas, paint: Paint) {
canvas.drawCircle(rectF.centerX(), rectF.centerY(), rectF.width() / 2, paint)
}
override fun onDrawDecoration(rectF: RectF, canvas: Canvas, paint: Paint) {
canvas.drawCircle(rectF.centerX(), rectF.centerY(), (rectF.width() - paint.strokeWidth) / 2, paint)
}
}
class RectangleDrawer : CropBoxDrawer {
override fun onDraw(rectF: RectF, canvas: Canvas, paint: Paint) {
canvas.drawRect(rectF, paint)
}
override fun onDrawDecoration(rectF: RectF, canvas: Canvas, paint: Paint) {
val realRectF = RectF(
rectF.left + paint.strokeWidth / 2,
rectF.top,
rectF.right - paint.strokeWidth / 2,
rectF.bottom
)
canvas.drawRect(realRectF, paint)
}
}
}

View File

@ -0,0 +1,35 @@
package com.gh.gamecenter.common.view.cropbox
import android.graphics.RectF
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
sealed class CropBoxStyle : Parcelable {
fun getRect(width: Int, height: Int, horizontalPadding: Int): RectF {
// 计算矩形区域的宽度
val boxWidth = width - 2 * horizontalPadding
val boxHeight = getBoxHeight(boxWidth)
val left = width - boxWidth - horizontalPadding
val top = (height - boxHeight) / 2
val right = left + boxWidth
val bottom = top + boxHeight
return RectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
}
abstract fun getBoxHeight(boxWidth: Int): Int
object Circle : CropBoxStyle() {
override fun getBoxHeight(boxWidth: Int): Int {
return boxWidth
}
}
class Rectangle(val radio: Float) : CropBoxStyle() {
override fun getBoxHeight(boxWidth: Int): Int {
return (boxWidth * radio).toInt()
}
}
}

View File

@ -0,0 +1,82 @@
package com.gh.gamecenter.common.view.cropbox
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.gh.gamecenter.common.utils.dip2px
class CropBoxView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
View(context, attrs, defStyleAttr) {
private var horizontalPadding = 0
private var boxStyle: CropBoxStyle = CropBoxStyle.Rectangle(1F)
private var drawer: CropBoxDrawer = CropBoxDrawer.RectangleDrawer()
private val paint by lazy {
Paint().apply {
isAntiAlias = true
isDither = true
color = Color.parseColor("#CC000000")
}
}
private val borderPaint by lazy {
Paint().apply {
style = Paint.Style.STROKE
isAntiAlias = true
setColor(Color.WHITE)
strokeWidth = 0.5F.dip2px().toFloat()
}
}
private val xFermode by lazy {
val mode = PorterDuff.Mode.CLEAR
PorterDuffXfermode(mode)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val rect = boxStyle.getRect(width, height, horizontalPadding)
val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), paint, Canvas.ALL_SAVE_FLAG)
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
paint.xfermode = xFermode
drawer.onDraw(rect, canvas, paint)
paint.xfermode = null
canvas.restoreToCount(layerId)
// 绘制裁剪框边框
drawer.onDrawDecoration(rect, canvas, borderPaint)
}
fun setHorizontalPaddingBoxStyle(horizontalPadding: Int, boxStyle: CropBoxStyle) {
this.horizontalPadding = horizontalPadding
this.boxStyle = boxStyle
drawer = createDrawer(boxStyle)
}
private val cropBoxAnimator by lazy {
CropBoxAnimator {
paint.color = it
postInvalidateOnAnimation()
}
}
fun changeDragState(isDragging: Boolean) {
cropBoxAnimator.changeDragState(isDragging)
}
private fun createDrawer(boxStyle: CropBoxStyle) = when (boxStyle) {
is CropBoxStyle.Circle -> CropBoxDrawer.CircleDrawer()
is CropBoxStyle.Rectangle -> CropBoxDrawer.RectangleDrawer()
}
}

View File

@ -249,15 +249,22 @@
<declare-styleable name="FadingEdgeLayout">
<attr name="fel_edge" format="integer">
<flag name="top" value="1"/>
<flag name="bottom" value="2"/>
<flag name="left" value="4"/>
<flag name="right" value="8"/>
<flag name="top" value="1" />
<flag name="bottom" value="2" />
<flag name="left" value="4" />
<flag name="right" value="8" />
</attr>
<attr name="fel_size_top" format="dimension"/>
<attr name="fel_size_bottom" format="dimension"/>
<attr name="fel_size_left" format="dimension"/>
<attr name="fel_size_right" format="dimension"/>
<attr name="fel_size_top" format="dimension" />
<attr name="fel_size_bottom" format="dimension" />
<attr name="fel_size_left" format="dimension" />
<attr name="fel_size_right" format="dimension" />
</declare-styleable>
<declare-styleable name="DividerView">
<attr name="divider_size" format="integer">
<flag name="thin" value="0" />
<flag name="medium" value="1" />
<flag name="regular" value="2" />
</attr>
</declare-styleable>
</resources>

View File

@ -0,0 +1,10 @@
package com.gh.gamecenter.core.provider
import android.content.Context
import android.content.Intent
import android.net.Uri
interface ICropImageProvider {
fun getCropImageIntent(data: List<Uri>, imageType: Int, entrance: String, context: Context): Intent?
}