diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4a38b82275..5dc4de1fba 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -717,6 +717,10 @@ android:name=".savegame.GameArchiveListActivity" android:screenOrientation="portrait" /> + + diff --git a/app/src/main/java/com/gh/common/constant/Config.java b/app/src/main/java/com/gh/common/constant/Config.java index dd3975990a..fff3065c17 100644 --- a/app/src/main/java/com/gh/common/constant/Config.java +++ b/app/src/main/java/com/gh/common/constant/Config.java @@ -248,6 +248,14 @@ public class Config { } } + public static boolean getUserInterestedGame() { + if (mNewApiSettingsEntity != null) { + return mNewApiSettingsEntity.getUserInterestedGame(); + } else { + return false; + } + } + @Nullable public static NewSettingsEntity getNewSettingsEntity() { if (mNewSettingsEntity == null) { diff --git a/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt b/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt index e9715fed98..59798989ed 100644 --- a/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt +++ b/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt @@ -1044,4 +1044,83 @@ object NewFlatLogUtils { } log(json, "event", false) } + + // 进入发现页详情 + fun logDiscoverDetailPageEnter(entrance: String, blockName: String = "") { + val json = json { + "event" to "discover_detail_page_enter" + "entrance" to entrance + if (blockName.isNotBlank()) { + "block_name" to blockName + } + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + // 点击设置偏好 + fun logInterestedGameClick(entrance: String, blockName: String = "") { + val json = json { + "event" to "discover_preference_settings_click" + "entrance" to entrance + if (blockName.isNotBlank()) { + "block_name" to blockName + } + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + // 进入游戏偏好页面 + fun logInterestedGamePageEnter(frequency: String, prefer: String, tendency: String, typeTags: JSONArray) { + val json = json { + "event" to "discover_preference_game_page_enter" + "frequency" to frequency + "prefer" to prefer + "tendency" to tendency + "type_tags" to typeTags + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + // 离开游戏偏好页面 + fun logInterestedGamePageExit( + frequency: String, + prefer: String, + tendency: String, + typeTags: JSONArray, + interval: Long + ) { + val json = json { + "event" to "discover_preference_game_page_quit" + "frequency" to frequency + "prefer" to prefer + "tendency" to tendency + "interval" to interval + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + // 展开感兴趣的分类弹窗 + fun logInterestedGameTypeDialogShow(typeTags: JSONArray) { + val json = json { + "event" to "discover_preference_type_tags_dialog_show" + "type_tags" to typeTags + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + // 关闭感兴趣的分类弹窗 + fun logInterestedGameTypeDialogClose(typeTags: JSONArray, interval: Long) { + val json = json { + "event" to "discover_preference_type_tags_dialog_close" + "type_tags" to typeTags + "interval" to interval + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryAdapter.kt b/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryAdapter.kt index ca4d351607..3fba89dfb4 100644 --- a/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryAdapter.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import android.widget.Space import android.widget.TextView import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.Config import com.gh.common.databind.BindingAdapters import com.gh.common.exposure.ExposureEvent import com.gh.common.exposure.ExposureSource @@ -34,6 +35,8 @@ import com.gh.gamecenter.databinding.DiscoveryGameItemBinding import com.gh.gamecenter.databinding.ItemRecommendInterestBinding import com.gh.gamecenter.databinding.ItemRecommendInterestFooterBinding import com.gh.gamecenter.databinding.ItemRecommendInterestImageBinding +import com.gh.gamecenter.discovery.DiscoveryFragment.Companion.INTERESTED_GAME_REQUEST_CODE +import com.gh.gamecenter.discovery.interestedgame.InterestedGameActivity import com.gh.gamecenter.entity.DiscoveryGameCardLabel import com.gh.gamecenter.entity.GameEntity import com.gh.gamecenter.eventbus.EBDownloadStatus @@ -45,6 +48,7 @@ import org.greenrobot.eventbus.EventBus class DiscoveryAdapter( context: Context, + private val mFragment: DiscoveryFragment, private val mViewModel: DiscoveryViewModel, private val mBaseExposureSource: ArrayList, private val mOuterSequence: Int, @@ -198,16 +202,35 @@ class DiscoveryAdapter( } } is DirectGameBlockViewHolder -> { - holder.binding.root.setBackgroundColor(R.color.background_white.toColor(mContext)) - holder.binding.root.setOnClickListener { - val navBar = MainWrapperRepository.getInstance().getNavBarLiveData().value - if (navBar == null || mContext is DiscoveryActivity) { - val blockData = HomeBottomBarHelper.getDefaultGameBarData() - mContext.startActivity(BlockActivity.getIntent(mContext, blockData, mEntrance)) - } else { - EventBus.getDefault().post(EBSkip(MainActivity.EB_SKIP_MAIN, MainWrapperFragment.INDEX_GAME)) + holder.binding.run { + val userInterestedGame = Config.getUserInterestedGame() + val padTop = if (userInterestedGame) 0 else 16F.dip2px() + root.setPadding(16F.dip2px(), padTop, 16F.dip2px(), 16F.dip2px()) + root.setBackgroundColor(R.color.background_white.toColor(mContext)) + interestedGameTv.goneIf(!userInterestedGame) { + interestedGameTv.text = mContext.getString(R.string.interested_game_footer_hint).fromHtml() + interestedGameTv.setOnClickListener { + mFragment.startActivityForResult( + InterestedGameActivity.getIntent( + mContext, + "发现页-底部" + ), + INTERESTED_GAME_REQUEST_CODE + ) + NewFlatLogUtils.logInterestedGameClick("发现页底部") + } + } + recommendIv.setOnClickListener { + val navBar = MainWrapperRepository.getInstance().getNavBarLiveData().value + if (navBar == null || mContext is DiscoveryActivity) { + val blockData = HomeBottomBarHelper.getDefaultGameBarData() + mContext.startActivity(BlockActivity.getIntent(mContext, blockData, mEntrance)) + } else { + EventBus.getDefault() + .post(EBSkip(MainActivity.EB_SKIP_MAIN, MainWrapperFragment.INDEX_GAME)) + } + NewFlatLogUtils.logDiscoverPageJumpGamesLibraries() } - NewFlatLogUtils.logDiscoverPageJumpGamesLibraries() } } diff --git a/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryFragment.kt b/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryFragment.kt index 159387885f..cd0c62e1d1 100644 --- a/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/discovery/DiscoveryFragment.kt @@ -1,5 +1,7 @@ package com.gh.gamecenter.discovery +import android.app.Activity +import android.content.Intent import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -149,6 +151,7 @@ class DiscoveryFragment : LazyListFragment, + private val mViewModel: InterestedGameViewModel, +) : BaseRecyclerAdapter(context) { + + private var countAndKey: Pair? = null + + init { + var dataIds = "" + mList.forEach { + dataIds += it.id + } + if (dataIds.isNotEmpty()) countAndKey = Pair(mList.size, dataIds) + } + + override fun getItemCount() = mList.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + InterestGameSubTagItemViewHolder(parent.toBinding()) + + override fun onBindViewHolder(holder: InterestGameSubTagItemViewHolder, position: Int) { + holder.binding.tagTv.run { + val tag = mList[position] + text = tag.name + isChecked = mViewModel.getTempSelectedTagList().contains(tag) + setOnClickListener { + isChecked = !isChecked + mViewModel.run { + isModify = true + if (isChecked) { + increaseSelectedCount() + addTempTag(tag) + } else { + decreaseSelectedCount() + removeTempTag(tag) + } + } + } + } + } + + fun checkResetData(update: List) { + var dataIds = "" + update.forEach { dataIds += it.id } + + mList = update + if (countAndKey?.first == update.size && countAndKey?.second != dataIds) { // 数量不变,内容发生改变 + notifyItemRangeChanged(0, itemCount) + } else if (countAndKey?.first != update.size) { // 数量发生改变 + notifyDataSetChanged() + } else { + notifyItemRangeChanged(0, itemCount) + } + + // 重新刷新数据标识 + countAndKey = Pair(update.size, dataIds) + } + + class InterestGameSubTagItemViewHolder(val binding: InterestedGameSubTagItemBinding) : + BaseRecyclerViewHolder(binding.root) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameActivity.kt b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameActivity.kt new file mode 100644 index 0000000000..e7a4f9e5f1 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameActivity.kt @@ -0,0 +1,39 @@ +package com.gh.gamecenter.discovery.interestedgame + +import android.content.Context +import android.content.Intent +import android.os.Bundle +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.utils.updateStatusBarColor + +/** + * 游戏偏好页 + */ +class InterestedGameActivity : ToolBarActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + updateStatusBarColor(R.color.background_white, R.color.background_white) + setNavigationTitle("游戏偏好") + } + + override fun onDarkModeChanged() { + super.onDarkModeChanged() + updateStatusBarColor(R.color.background_white, R.color.background_white) + } + + companion object { + fun getIntent(context: Context, entrance: String): Intent { + val bundle = Bundle() + bundle.putString(EntranceConsts.KEY_ENTRANCE, entrance) + return getTargetIntent( + context, + InterestedGameActivity::class.java, + InterestedGameFragment::class.java, + bundle + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameFragment.kt b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameFragment.kt new file mode 100644 index 0000000000..39fc39fbee --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameFragment.kt @@ -0,0 +1,295 @@ +package com.gh.gamecenter.discovery.interestedgame + +import android.app.Activity +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.CheckedTextView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.content.res.AppCompatResources +import androidx.fragment.app.viewModels +import com.gh.common.util.NewFlatLogUtils +import com.gh.gamecenter.R +import com.gh.gamecenter.common.base.fragment.ToolbarFragment +import com.gh.gamecenter.common.utils.* +import com.gh.gamecenter.databinding.FragmentInterestedGameBinding +import com.gh.gamecenter.entity.InterestedGameEntity +import com.gh.gamecenter.entity.InterestedGameEntity.TypeTag.Tag +import com.gh.gamecenter.entity.InterestedGamePostEntity +import org.json.JSONArray + +class InterestedGameFragment : ToolbarFragment() { + + private val mViewModel: InterestedGameViewModel by viewModels() + private val mBinding by lazy { FragmentInterestedGameBinding.inflate(layoutInflater) } + private val mFrequencyViewList by lazy { + listOf( + mBinding.frequencyOne, + mBinding.frequencyTwo, + mBinding.frequencyThree + ) + } + private val mConditionViewList by lazy { + listOf( + mBinding.conditionOne, + mBinding.conditionTwo + ) + } + private val mTendencyViewList by lazy { + listOf( + mBinding.tendencyOne, + mBinding.tendencyTwo, + mBinding.tendencyThree + ) + } + private val mFrequencyList by lazy { + listOf( + "daily", + "holiday", + "sometime" + ) + } + private val mConditionList by lazy { + listOf( + "new", + "old" + ) + } + private val mTendencyList by lazy { + listOf( + "single", + "friend", + "others" + ) + } + private var mCurrentFrequencyIndex = -1 + private var mCurrentConditionIndex = -1 + private var mCurrentTendencyIndex = -1 + private val mSelectedTagViewList = arrayListOf() + private var mDialogStayTime = 0L + + override fun getLayoutId() = 0 + + override fun getInflatedLayout() = mBinding.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initObserver() + initView() + } + + override fun onStop() { + super.onStop() + val frequency = + if (mCurrentFrequencyIndex == -1) "" else mFrequencyViewList[mCurrentFrequencyIndex].text.toString() + val prefer = + if (mCurrentConditionIndex == -1) "" else mConditionViewList[mCurrentConditionIndex].text.toString() + val tendency = if (mCurrentTendencyIndex == -1) "" else mTendencyViewList[mCurrentTendencyIndex].text.toString() + val stayTime = (System.currentTimeMillis() - startPageTime) / 1000 + NewFlatLogUtils.logInterestedGamePageExit( + frequency, + prefer, + tendency, + JSONArray(mViewModel.getSelectedTagIdList()), + stayTime + ) + } + + private fun initObserver() { + mViewModel.interestedGameData.observeNonNull(this) { + initStatus(it) + } + mViewModel.postResult.observeNonNull(this) { + if (it) { + toast("已根据你的偏好优化推荐机制~") + requireActivity().setResult(Activity.RESULT_OK) + requireActivity().finish() + } else { + toast("保存失败") + } + } + mViewModel.selectedTagLisLiveData.observeNonNull(this) { + removeAllTagView() + it.forEach { tag -> + addTagView(tag) + } + } + } + + private fun removeAllTagView() { + mSelectedTagViewList.clear() + if (mBinding.typeFlexBox.childCount > 1) { + mBinding.typeFlexBox.removeViews(1, mBinding.typeFlexBox.childCount - 1) + } + } + + private fun initView() { + mBinding.run { + typeFlexBox.addView(getAddTypeView()) + mFrequencyViewList.forEachIndexed { index, view -> + view.setOnClickListener { + if (mCurrentFrequencyIndex != index) { + mViewModel.isModify = true + mCurrentFrequencyIndex = index + updateViewList(mFrequencyViewList, index) + } + } + } + mConditionViewList.forEachIndexed { index, view -> + view.setOnClickListener { + if (mCurrentConditionIndex != index) { + mViewModel.isModify = true + mCurrentConditionIndex = index + updateViewList(mConditionViewList, index) + } + } + } + mTendencyViewList.forEachIndexed { index, view -> + view.setOnClickListener { + if (mCurrentTendencyIndex != index) { + mViewModel.isModify = true + mCurrentTendencyIndex = index + updateViewList(mTendencyViewList, index) + } + } + } + saveBtn.setOnClickListener { + postInterestedGameData() + } + } + } + + private fun initStatus(entity: InterestedGameEntity) { + mCurrentFrequencyIndex = mFrequencyList.indexOf(entity.playFrequency) + mCurrentConditionIndex = mConditionList.indexOf(entity.gameCondition) + mCurrentTendencyIndex = mTendencyList.indexOf(entity.playType) + updateViewList(mFrequencyViewList, mCurrentFrequencyIndex) + updateViewList(mConditionViewList, mCurrentConditionIndex) + updateViewList(mTendencyViewList, mCurrentTendencyIndex) + + val frequency = + if (mCurrentFrequencyIndex == -1) "" else mFrequencyViewList[mCurrentFrequencyIndex].text.toString() + val prefer = + if (mCurrentConditionIndex == -1) "" else mConditionViewList[mCurrentConditionIndex].text.toString() + val tendency = if (mCurrentTendencyIndex == -1) "" else mTendencyViewList[mCurrentTendencyIndex].text.toString() + NewFlatLogUtils.logInterestedGamePageEnter( + frequency, + prefer, + tendency, + JSONArray(entity.tags) + ) + } + + private fun updateViewList(list: List, selectedIndex: Int) { + if (selectedIndex == -1) return + + list.forEachIndexed { index, view -> + view.isChecked = index == selectedIndex + } + + if (listOf(mCurrentFrequencyIndex, mCurrentConditionIndex, mCurrentTendencyIndex).all { it != -1 }) { + mBinding.saveBtn.alpha = 1.0F + } + } + + private fun getAddTypeView(): View { + return TextView(requireContext()).apply { + layoutParams = ViewGroup.MarginLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + 32F.dip2px() + ).apply { topMargin = 8F.dip2px() } + setPadding(12F.dip2px(), 0, 12F.dip2px(), 0) + setBackgroundResource(R.drawable.bg_shape_2496ff_alpha_10_radius_8) + gravity = Gravity.CENTER + setDrawableStart(R.drawable.ic_interested_game_add) + compoundDrawablePadding = 4F.dip2px() + setTextColor(R.color.theme_font.toColor(requireContext())) + textSize = 12F + text = "添加类型" + setOnClickListener { + if (mViewModel.getTypeTags().isNotEmpty()) { + InterestedGameTypeDialogFragment.show(requireActivity() as AppCompatActivity, mViewModel) { + val stayTime = (System.currentTimeMillis() - mDialogStayTime) / 1000 + NewFlatLogUtils.logInterestedGameTypeDialogClose( + JSONArray(mViewModel.getSelectedTagIdList()), + stayTime + ) + } + mDialogStayTime = System.currentTimeMillis() + NewFlatLogUtils.logInterestedGameTypeDialogShow(JSONArray(mViewModel.getSelectedTagIdList())) + } + } + } + } + + private fun addTagView(tag: Tag) { + val tagView = TextView(requireContext()).apply { + layoutParams = ViewGroup.MarginLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + 32F.dip2px() + ).apply { + topMargin = 8F.dip2px() + leftMargin = 8F.dip2px() + } + setPadding(12F.dip2px(), 0, 12F.dip2px(), 0) + setBackgroundResource(R.drawable.border_round_transparent_stroke_divider) + gravity = Gravity.CENTER + setDrawableEnd(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_interested_game_tag_delete)) + compoundDrawablePadding = 4F.dip2px() + setTextColor(R.color.text_subtitle.toColor(requireContext())) + text = tag.name + setOnClickListener { + removeTagView(tag) + mViewModel.decreaseSelectedCount() + mViewModel.removeTag(tag) + } + } + mSelectedTagViewList.add(tagView) + mBinding.typeFlexBox.addView(tagView) + } + + private fun removeTagView(tag: Tag) { + val index = mViewModel.getTagIndex(tag) + if (index < mSelectedTagViewList.size) { + mBinding.typeFlexBox.removeView(mSelectedTagViewList[index]) + mSelectedTagViewList.removeAt(index) + } + } + + override fun onBackPressed(): Boolean { + if (mViewModel.isModify) { + showSaveDialog() + return true + } + return super.onBackPressed() + } + + private fun showSaveDialog() { + DialogHelper.showDialog( + requireContext(), + "提示", + "是否保存本次的修改?", + "保存", + "取消", + { postInterestedGameData() }, + { requireActivity().finish() }, + extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true) + ) + } + + private fun postInterestedGameData() { + if (listOf(mCurrentFrequencyIndex, mCurrentConditionIndex, mCurrentTendencyIndex).any { it == -1 }) { + toast("请完善你的游戏偏好") + return + } + val postEntity = InterestedGamePostEntity( + mFrequencyList[mCurrentFrequencyIndex], + mConditionList[mCurrentConditionIndex], + mTendencyList[mCurrentTendencyIndex], + mViewModel.getSelectedTagIdList() + ) + mViewModel.postInterestedGameData(postEntity) + } +} diff --git a/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameTypeAdapter.kt b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameTypeAdapter.kt new file mode 100644 index 0000000000..17b95cdcd4 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameTypeAdapter.kt @@ -0,0 +1,44 @@ +package com.gh.gamecenter.discovery.interestedgame + +import android.content.Context +import android.view.ViewGroup +import androidx.recyclerview.widget.GridLayoutManager +import com.gh.gamecenter.R +import com.gh.gamecenter.common.base.BaseRecyclerViewHolder +import com.gh.gamecenter.common.utils.dip2px +import com.gh.gamecenter.common.utils.toBinding +import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration +import com.gh.gamecenter.databinding.InterestedGameTypeItemBinding +import com.lightgame.adapter.BaseRecyclerAdapter + +class InterestedGameTypeAdapter( + context: Context, + private val mViewModel: InterestedGameViewModel +) : BaseRecyclerAdapter(context) { + + override fun getItemCount() = mViewModel.getTypeTags().size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + InterestedGameTypeViewHolder(parent.toBinding()) + + override fun onBindViewHolder(holder: InterestedGameTypeViewHolder, position: Int) { + holder.binding.run { + val padTop = if (position == 0) 16F.dip2px() else 0F.dip2px() + root.setPadding(16F.dip2px(), padTop, 16F.dip2px(), 16F.dip2px()) + val entity = mViewModel.getTypeTags()[position] + typeTv.text = entity.name + subTagRv.run { + if (adapter is InterestGameSubTagAdapter) { + (adapter as InterestGameSubTagAdapter).checkResetData(entity.tags) + } else { + layoutManager = GridLayoutManager(mContext, 4) + adapter = InterestGameSubTagAdapter(mContext, entity.tags, mViewModel) + addItemDecoration(GridSpacingItemColorDecoration(mContext, 8, 8, R.color.transparent)) + } + } + } + } + + inner class InterestedGameTypeViewHolder(val binding: InterestedGameTypeItemBinding) : + BaseRecyclerViewHolder(binding.root) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameTypeDialogFragment.kt b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameTypeDialogFragment.kt new file mode 100644 index 0000000000..edc1d27c96 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameTypeDialogFragment.kt @@ -0,0 +1,106 @@ +package com.gh.gamecenter.discovery.interestedgame + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.common.base.fragment.BaseDialogFragment +import com.gh.gamecenter.common.utils.dip2px +import com.gh.gamecenter.common.utils.observeNonNull +import com.gh.gamecenter.common.utils.setRootBackgroundDrawable +import com.gh.gamecenter.common.view.FixLinearLayoutManager +import com.gh.gamecenter.core.utils.EmptyCallback +import com.gh.gamecenter.databinding.DialogInterestedGameTypeBinding +import com.halo.assistant.HaloApp + +class InterestedGameTypeDialogFragment( + private val mViewModel: InterestedGameViewModel, + private val dismissCallback: EmptyCallback +) : BaseDialogFragment() { + + private val mBinding by lazy { DialogInterestedGameTypeBinding.inflate(layoutInflater) } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return mBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mViewModel.initTempTagList() + mViewModel.selectedCountLiveData.observeNonNull(this) { + updateConfirmBtnText(it) + } + mBinding.run { + updateConfirmBtnText(mViewModel.getSelectedCount()) + cancelTv.setOnClickListener { + dismissAllowingStateLoss() + } + typeRv.run { + layoutManager = FixLinearLayoutManager(requireContext()) + adapter = InterestedGameTypeAdapter(requireContext(), mViewModel) + } + clearBtn.setOnClickListener { + if (mViewModel.getSelectedCount() > 0) { + mViewModel.resetTemp() + typeRv.adapter?.notifyDataSetChanged() + } + } + confirmBtn.setOnClickListener { + mViewModel.confirmTempList() + dismissAllowingStateLoss() + } + } + } + + private fun updateConfirmBtnText(count: Int) { + val suffix = if (count > 0) "(${count})" else "" + mBinding.confirmBtn.text = "确认$suffix" + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val createDialog = super.onCreateDialog(savedInstanceState) + createDialog.setCanceledOnTouchOutside(true) + val window = createDialog.window + window?.setGravity(Gravity.BOTTOM) + window?.setWindowAnimations(R.style.community_publication_animation) + return createDialog + } + + override fun onStart() { + super.onStart() + val width = HaloApp.getInstance().application.resources.displayMetrics.widthPixels + val height = 384F.dip2px() + dialog?.window?.setLayout(width, height) + } + + override fun onDarkModeChanged() { + super.onDarkModeChanged() + mBinding.run { + root.setRootBackgroundDrawable(R.drawable.background_shape_white_radius_12_top_only) + } + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + dismissCallback.onCallback() + } + + companion object { + + fun show( + activity: AppCompatActivity, + viewModel: InterestedGameViewModel, + dismissCallback: EmptyCallback + ) { + InterestedGameTypeDialogFragment(viewModel, dismissCallback).show( + activity.supportFragmentManager, + InterestedGameTypeDialogFragment::class.java.name + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameViewModel.kt b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameViewModel.kt new file mode 100644 index 0000000000..bbf12e7ad9 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/discovery/interestedgame/InterestedGameViewModel.kt @@ -0,0 +1,138 @@ +package com.gh.gamecenter.discovery.interestedgame + +import android.annotation.SuppressLint +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import com.gh.gamecenter.common.retrofit.BiResponse +import com.gh.gamecenter.common.utils.singleToMain +import com.gh.gamecenter.common.utils.toRequestBody +import com.gh.gamecenter.entity.InterestedGameEntity +import com.gh.gamecenter.entity.InterestedGameEntity.TypeTag.Tag +import com.gh.gamecenter.entity.InterestedGamePostEntity +import com.gh.gamecenter.retrofit.RetrofitManager +import okhttp3.ResponseBody +import java.util.* + +class InterestedGameViewModel(application: Application) : AndroidViewModel(application) { + + private val mApi = RetrofitManager.getInstance().newApi + private var mTypeTags: List = listOf() + private val mSelectedTagList = arrayListOf() + private val mTempSelectedTagList = arrayListOf() + private var mSelectedCount = 0 + var interestedGameData = MutableLiveData() + var postResult = MutableLiveData() + var selectedCountLiveData = MutableLiveData() + var selectedTagLisLiveData = MutableLiveData>() + var isModify = false + + init { + getInterestedGameData() + } + + fun getTypeTags() = mTypeTags + + fun getSelectedCount() = mSelectedCount + + fun getSelectedTagList() = mSelectedTagList + + fun getTempSelectedTagList() = mTempSelectedTagList + + fun getSelectedTagIdList() = mSelectedTagList.map { it.id } + + fun getTagIndex(tag: Tag) = mSelectedTagList.indexOf(tag) + + @SuppressLint("CheckResult") + fun getInterestedGameData() { + mApi.interestedGame + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: InterestedGameEntity) { + mTypeTags = data.typeTag.filter { it.tags.isNotEmpty() } + initSelectedTags(data.tags) + interestedGameData.postValue(data) + } + }) + } + + @SuppressLint("CheckResult") + fun postInterestedGameData(data: InterestedGamePostEntity) { + mApi.postInterestedGame(data.toRequestBody()) + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + postResult.postValue(true) + } + + override fun onFailure(exception: Exception) { + postResult.postValue(false) + } + }) + } + + fun initSelectedTags(tagIdList: List) { + mTypeTags.forEach { + it.tags.forEach { tag -> + if (tagIdList.contains(tag.id)) { + addTag(tag) + } + } + } + mSelectedCount = mSelectedTagList.size + if (mSelectedTagList.isNotEmpty()) postSelectedTagList() + } + + fun postSelectedTagList() { + selectedTagLisLiveData.postValue(mSelectedTagList) + } + + fun postSelectedCount() { + selectedCountLiveData.postValue(mSelectedCount) + } + + fun increaseSelectedCount() { + mSelectedCount++ + postSelectedCount() + } + + fun decreaseSelectedCount() { + if (mSelectedCount > 0) mSelectedCount-- + postSelectedCount() + } + + fun addTag(tag: Tag) { + mSelectedTagList.add(tag) + } + + fun removeTag(tag: Tag) { + mSelectedTagList.remove(tag) + } + + fun addTempTag(tag: Tag) { + mTempSelectedTagList.add(tag) + } + + fun removeTempTag(tag: Tag) { + mTempSelectedTagList.remove(tag) + } + + fun confirmTempList() { + mSelectedTagList.clear() + mSelectedTagList.addAll(mTempSelectedTagList) + postSelectedTagList() + } + + fun initTempTagList() { + mTempSelectedTagList.clear() + mTempSelectedTagList.addAll(mSelectedTagList) + mSelectedCount = mTempSelectedTagList.size + postSelectedCount() + } + + fun resetTemp() { + mTempSelectedTagList.clear() + mSelectedCount = 0 + postSelectedCount() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/DiscoveryCardEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/DiscoveryCardEntity.kt new file mode 100644 index 0000000000..7e12bd8fbd --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/DiscoveryCardEntity.kt @@ -0,0 +1,6 @@ +package com.gh.gamecenter.entity + +data class DiscoveryCardEntity( + val games: ArrayList = arrayListOf(), + val label: List = listOf() +) \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/DiscoveryGameCardEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/DiscoveryGameCardEntity.kt index f7c1568edf..9bc4d43ea7 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/DiscoveryGameCardEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/DiscoveryGameCardEntity.kt @@ -8,7 +8,7 @@ data class DiscoveryGameCardEntity( @SerializedName("game_tags") val gameTags: ArrayList = arrayListOf(), @SerializedName("data") - val games: ArrayList = arrayListOf() + var games: ArrayList = arrayListOf() ) diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt index f196cdb8cd..e9097c7636 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt @@ -628,6 +628,14 @@ data class GameEntity( fun isSpecialDownload() = RegionSettingHelper.shouldThisGameShowSpecialDownload(id) + fun getTypeName() = + when (type) { + "filter" -> "算法过滤" + "recommend" -> "专题推荐" + "ad" -> "游戏广告" + else -> "" + } + fun toSimpleGame(): SimpleGame { val simpleGame = SimpleGame() simpleGame.id = id diff --git a/app/src/main/java/com/gh/gamecenter/entity/InterestedGameEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/InterestedGameEntity.kt new file mode 100644 index 0000000000..b60331c5e7 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/InterestedGameEntity.kt @@ -0,0 +1,50 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class InterestedGameEntity( + @SerializedName("_id") + var id: String = "", + @SerializedName("play_frequency") + var playFrequency: String = "", // 玩游戏的评率:每天daily,节假日holiday,偶尔sometime + @SerializedName("game_new_old") + var gameCondition: String = "", // 游戏新旧:新new,旧old + @SerializedName("play_type") + var playType: String = "", // 玩游戏的类型:单人-single, 朋友-friend, 其他-others + var tags: List = listOf(), // 关注的标签_id + @SerializedName("type_tag") + var typeTag: List = listOf(), // 后台设置的分类及标签 +) : Parcelable { + + @Parcelize + data class TypeTag( + @SerializedName("_id") + var id: String = "", + var name: String = "", + var tags: ArrayList = arrayListOf() + ) : Parcelable { + + @Parcelize + data class Tag( + @SerializedName("_id") + var id: String = "", + var name: String = "", + var style: TagStyleEntity = TagStyleEntity(), + ) : Parcelable + + } +} + +@Parcelize +data class InterestedGamePostEntity( + @SerializedName("play_frequency") + var playFrequency: String = "", // 玩游戏的评率:每天daily,节假日holiday,偶尔sometime + @SerializedName("game_new_old") + var gameCondition: String = "", // 游戏新旧:新new,旧old + @SerializedName("play_type") + var playType: String = "", // 玩游戏的类型:单人-single, 朋友-friend, 其他-others + var tags: List = listOf(), // 关注的标签_id +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt index fdfd6b438f..166fd7a5e9 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt @@ -8,7 +8,9 @@ data class NewApiSettingsEntity( var simulator: SimulatorEntity? = null, @SerializedName("start_ad") var startAd: StartupAdEntity? = null,//开屏图片广告 - var startup: StartupAdEntity? = null//启动文案广告 + var startup: StartupAdEntity? = null,//启动文案广告 + @SerializedName("user_interested_game") + var userInterestedGame: Boolean = false //偏好设置状态开关 ) { /** * diff --git a/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt b/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt index d9cea6299e..2396978056 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt @@ -18,19 +18,19 @@ import com.gh.common.xapk.XapkUnzipStatus import com.gh.download.DownloadManager import com.gh.gamecenter.R import com.gh.gamecenter.common.base.fragment.LazyFragment -import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.baselist.LoadStatus +import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.constant.EntranceConsts +import com.gh.gamecenter.common.eventbus.EBReuse +import com.gh.gamecenter.common.utils.* import com.gh.gamecenter.common.view.FixLinearLayoutManager +import com.gh.gamecenter.core.AppExecutor +import com.gh.gamecenter.core.utils.MD5Utils +import com.gh.gamecenter.core.utils.SPUtils import com.gh.gamecenter.databinding.FragmentGameBinding import com.gh.gamecenter.eventbus.EBDownloadStatus import com.gh.gamecenter.eventbus.EBPackage import com.gh.gamecenter.eventbus.EBUISwitch -import com.gh.gamecenter.common.eventbus.EBReuse -import com.gh.gamecenter.common.utils.* -import com.gh.gamecenter.core.AppExecutor -import com.gh.gamecenter.core.utils.MD5Utils -import com.gh.gamecenter.core.utils.SPUtils import com.gh.gamecenter.fragment.MainWrapperFragment import com.gh.gamecenter.game.data.GameItemData import com.gh.gamecenter.home.video.ScrollCalculatorHelper @@ -300,7 +300,7 @@ class GameFragment : LazyFragment() { if (::mListAdapter.isInitialized) { val data = mListAdapter.getGameEntityByPackage(busFour.packageName) for (gameAndPosition in data) { - mListAdapter.notifyChildItem(gameAndPosition.position) + mListAdapter.notifyChildItem(gameAndPosition.position, busFour.packageName) } } } diff --git a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt index 9af9385ef5..a75e6fe4a7 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt @@ -69,6 +69,8 @@ import com.gh.gamecenter.game.vertical.OnPagerSnapScrollListener import com.gh.gamecenter.home.BlankDividerViewHolder import com.gh.gamecenter.home.HomeDividerViewHolder import com.gh.gamecenter.home.HomeGameItemViewHolder +import com.gh.gamecenter.home.discovercard.DiscoverCardGameAdapter +import com.gh.gamecenter.home.discovercard.HomeDiscoverCardViewHolder import com.gh.gamecenter.home.gamecollection.HomeGameCollectionViewHolder import com.gh.gamecenter.servers.GameServersActivity import com.gh.gamecenter.subject.SubjectActivity @@ -131,6 +133,7 @@ class GameFragmentAdapter( if (itemData.bigImageRecommend != null) return ItemViewType.BIG_IMAGE_RECOMMEND if (itemData.attachGame != null) return ItemViewType.GAME_ITEM if (itemData.lineDivider != null) return ItemViewType.DIVIDER_ITEM + if (itemData.discoverCard != null) return ItemViewType.DISCOVER_CARD return ItemViewType.LOADING } @@ -168,6 +171,7 @@ class GameFragmentAdapter( ItemViewType.BIG_IMAGE_RECOMMEND -> BigImageRecommendViewHolder(parent.toBinding()) ItemViewType.GAME_ITEM -> HomeGameItemViewHolder(parent.toBinding()) ItemViewType.DIVIDER_ITEM -> HomeDividerViewHolder(parent.toBinding()) + ItemViewType.DISCOVER_CARD -> HomeDiscoverCardViewHolder(parent.toBinding()) else -> GameItemViewHolder(GameItemBinding.bind(mLayoutInflater.inflate(R.layout.game_item, parent, false))) } @@ -196,6 +200,7 @@ class GameFragmentAdapter( is BigImageRecommendViewHolder -> bindBigImageRecommend(holder, position) is HomeGameItemViewHolder -> bindAttachGame(holder, position) is HomeDividerViewHolder -> holder.bindView(mItemDataList[position].lineDivider ?: 1F) + is HomeDiscoverCardViewHolder -> bindDiscoverCard(holder, position) } } @@ -1225,6 +1230,52 @@ class GameFragmentAdapter( } } + private fun bindDiscoverCard(holder: HomeDiscoverCardViewHolder, position: Int) { + val item = mItemDataList[position] + val discoverCard = item.discoverCard!! + val binding = holder.binding + val exposureEventList = arrayListOf() + val listExposureEventList = arrayListOf() + val spanCount = 3 + + val snapHelper = holder.bindView(item.discoverCard!!, "版块", mViewModel.blockData?.name ?: "") + + val exposureClosure: (Int) -> Unit = { + val gameList = discoverCard.games + runOnIoThread(true) { + listExposureEventList.clear() + + val startOffset = it * spanCount + val endOffset = if (startOffset + spanCount >= gameList.size) { + gameList.size + } else { + startOffset + spanCount + } + for (i in startOffset until endOffset) { + val event = ExposureEvent.createEventWithSourceConcat( + gameEntity = gameList[i].apply { + outerSequence = position + sequence = i + }, + basicSource = mBasicExposureSource, + source = listOf(ExposureSource("发现", gameList[i].getTypeName())) + ) + gameList[i].exposureEvent = event + listExposureEventList.add(event) + } + exposureEventList.addAll(listExposureEventList) + } + } + exposureClosure(0) + + binding.recyclerView.addOnScrollListener(OnPagerSnapScrollListener(snapHelper) { + exposureClosure(it) + }) + + item.exposureEventList = exposureEventList + + } + override fun getItemCount(): Int { return if (mItemDataList.size > 0) mItemDataList.size + 1 else mItemDataList.size } @@ -1236,12 +1287,15 @@ class GameFragmentAdapter( val entryMap = gameAndPosition.entity.getEntryMap() entryMap[download.platform] = download } - if (getItemViewType(gameAndPosition.position) == ItemViewType.VERTICAL_SLIDE_ITEM) { + if (getItemViewType(gameAndPosition.position) == ItemViewType.VERTICAL_SLIDE_ITEM + || getItemViewType(gameAndPosition.position) == ItemViewType.DISCOVER_CARD + ) { val view = layoutManager?.findViewByPosition(gameAndPosition.position) val recyclerView = view?.findViewById(R.id.recycler_view) when (val adapter = recyclerView?.adapter) { is GameVerticalAdapter -> adapter.notifyItemByDownload(download) is RankCollectionAdapter -> adapter.notifyItemByDownload(download) + is DiscoverCardGameAdapter -> adapter.notifyItemByDownload(download) } } else { notifyItemChanged(gameAndPosition.position) @@ -1256,20 +1310,21 @@ class GameFragmentAdapter( gameAndPosition.entity.getEntryMap().remove(status.platform) } - notifyChildItem(gameAndPosition.position) + notifyChildItem(gameAndPosition.position, status.packageName) } } - fun notifyChildItem(position: Int) { - if (getItemViewType(position) == ItemViewType.VERTICAL_SLIDE_ITEM || - getItemViewType(position) == ItemViewType.RANK_COLLECTION + fun notifyChildItem(position: Int, packageName: String) { + if (getItemViewType(position) == ItemViewType.VERTICAL_SLIDE_ITEM + || getItemViewType(position) == ItemViewType.RANK_COLLECTION + || getItemViewType(position) == ItemViewType.DISCOVER_CARD ) { val view = layoutManager?.findViewByPosition(position) val recyclerView = view?.findViewById(R.id.recycler_view) - if (recyclerView?.adapter is RankCollectionAdapter) { - (recyclerView.adapter as RankCollectionAdapter).notifyChildItem() - } else { - recyclerView?.adapter?.notifyDataSetChanged() + when (val adapter = recyclerView?.adapter) { + is RankCollectionAdapter -> adapter.notifyChildItem() + is DiscoverCardGameAdapter -> adapter.notifyChildItem(packageName) + else -> recyclerView?.adapter?.notifyDataSetChanged() } } else { notifyItemChanged(position) @@ -1323,6 +1378,18 @@ class GameFragmentAdapter( continue } + val discoverCard = mItemDataList[position].discoverCard + if (discoverCard != null) { + discoverCard.games.forEach { + for (apkEntity in it.getApk()) { + if (apkEntity.packageName == packageName) { + positionList.add(GameAndPosition(it, position)) + } + } + } + continue + } + val image = mItemDataList[position].image if (image != null) positionList.add(GameAndPosition(image, position)) } diff --git a/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt b/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt index d99c9ae68d..902b31a0bd 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt @@ -33,6 +33,7 @@ import com.gh.gamecenter.home.BlankDividerViewHolder import com.gh.gamecenter.retrofit.RetrofitManager import com.gh.gamecenter.setting.view.SettingsFragment import com.lightgame.utils.Utils +import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import retrofit2.HttpException @@ -51,6 +52,8 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt private val mSubjectChangedMap: ArrayMap> = ArrayMap() // 存储换一换的数据 private val mSubjectRefreshMap: ArrayMap> = ArrayMap() // 存储专题刷新数据 private var mSmartSubject: SubjectEntity? = null // 猜你喜欢专题 + private var mDiscoveryGameCard: DiscoveryGameCardEntity? = null + private var mDiscoveryGameCardLabels: List? = null private var mSubjectPage = 1 // 专题分页 private var mIsLoading = false @@ -470,6 +473,71 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt transformationItemData() } + // 获取发现卡片数据 + private fun getDiscoverCardData() { + val discoveryGamesObservable = mSensitiveApi.getDiscoveryGames(1, mapOf("refresh" to "true")) + val cardLabelsObservable = mSensitiveApi.cardLabels + when { + mDiscoveryGameCard == null && mDiscoveryGameCardLabels == null -> { + Observable.mergeDelayError(cardLabelsObservable, discoveryGamesObservable) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onNext(response: Any) { + when (response) { + is ArrayList<*> -> mDiscoveryGameCardLabels = + response as ArrayList + is DiscoveryGameCardEntity -> mDiscoveryGameCard = + response.apply { games = ArrayList(games.take(18)) } + } + } + + override fun onComplete() { + filterDiscoveryLabel() + transformationItemData() + } + }) + } + mDiscoveryGameCard == null -> { + discoveryGamesObservable.compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: DiscoveryGameCardEntity?) { + mDiscoveryGameCard = response?.apply { games = ArrayList(games.take(18)) } + filterDiscoveryLabel() + transformationItemData() + } + }) + } + mDiscoveryGameCardLabels == null -> { + cardLabelsObservable.compose(observableToMain()) + .subscribe(object : Response>() { + override fun onResponse(response: ArrayList?) { + super.onResponse(response) + mDiscoveryGameCardLabels = response + filterDiscoveryLabel() + transformationItemData() + } + }) + } + } + } + + // 过滤分组,现只需要"卡片一"分组 + private fun filterDiscoveryLabel() { + mDiscoveryGameCardLabels = mDiscoveryGameCardLabels?.filter { it.card == "卡片一" } + // 如果link_type为空需要在mGameTags中依次获取 + val gameTags = mDiscoveryGameCard?.gameTags + if (gameTags.isNullOrEmpty()) return + mDiscoveryGameCardLabels?.forEachIndexed { index, label -> + if (gameTags.size > index && label.type.isNullOrEmpty()) { + val gameTag = gameTags[index] + label.link = gameTag.link + label.type = gameTag.type + label.linkText = gameTag.linkText + label.title = gameTag.linkText + } + } + } + private fun transformationItemData() { mItemDataListCache.clear() positionAndPackageMap.clear() @@ -833,6 +901,24 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt continue } + if (subjectEntity.type == "explore") { + if (mDiscoveryGameCard == null || mDiscoveryGameCardLabels == null) { + getDiscoverCardData() + } else { + val itemItemData = GameItemData().apply { + discoverCard = DiscoveryCardEntity( + mDiscoveryGameCard?.games ?: arrayListOf(), + mDiscoveryGameCardLabels ?: listOf() + ) + } + mItemDataListCache.add(itemItemData) + mDiscoveryGameCard?.games?.forEach { + addGamePositionAndPackage(it) + } + } + continue + } + appendAdditionalInfoToSubjectGame(subjectEntity, index) } diff --git a/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt b/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt index 5a0beeb4ab..ab174ea15b 100644 --- a/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt +++ b/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt @@ -2,10 +2,7 @@ package com.gh.gamecenter.game.data import com.gh.common.exposure.ExposureEvent import com.gh.gamecenter.common.entity.LinkEntity -import com.gh.gamecenter.entity.GameEntity -import com.gh.gamecenter.entity.GameNavigationEntity -import com.gh.gamecenter.entity.SubjectEntity -import com.gh.gamecenter.entity.SubjectRecommendEntity +import com.gh.gamecenter.entity.* import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData class GameItemData { @@ -35,6 +32,8 @@ class GameItemData { var bigImageRecommend: SubjectEntity? = null //提问、帖子、视频帖、文章 + var discoverCard: DiscoveryCardEntity? = null // 发现页卡片 + var blankDivider: Float? = null // 空白的空间补全item var lineDivider: Float? = null diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt b/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt index 59957a8204..53facec106 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt @@ -30,8 +30,11 @@ import com.gh.gamecenter.game.horizontal.GameHorizontalAdapter import com.gh.gamecenter.game.horizontal.GameHorizontalSlideAdapter import com.gh.gamecenter.game.rank.RankCollectionAdapter import com.gh.gamecenter.game.vertical.GameVerticalAdapter +import com.gh.gamecenter.game.vertical.OnPagerSnapScrollListener import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity import com.gh.gamecenter.home.amway.HomeAmwayListViewHolder +import com.gh.gamecenter.home.discovercard.DiscoverCardGameAdapter +import com.gh.gamecenter.home.discovercard.HomeDiscoverCardViewHolder import com.gh.gamecenter.home.gamecollection.HomeGameCollectionViewHolder import com.gh.gamecenter.home.slide.HomeSlideListAdapter import com.gh.gamecenter.home.slide.HomeSlideListViewHolder @@ -143,6 +146,10 @@ class HomeFragmentAdapter( return oldItem.blankDivider == newItem.blankDivider } + if (oldItem?.discoverCard != null && newItem?.discoverCard != null) { + return oldItem.discoverCard == newItem.discoverCard + } + return false } @@ -166,6 +173,7 @@ class HomeFragmentAdapter( if (itemData.amway != null) return AMWAY_ITEM if (itemData.gameCollection != null) return GAME_COLLECTION_ITEM if (itemData.recentVGame != null) return RECENT_V_GAME + if (itemData.discoverCard != null) return DISCOVER_CARD if (itemData.lineDivider != null) return DIVIDER_ITEM if (itemData.unknownData != null) return UNKNOWN_ITEM @@ -181,6 +189,7 @@ class HomeFragmentAdapter( GAME_COLLECTION_ITEM -> HomeGameCollectionViewHolder(parent.toBinding()) DIVIDER_ITEM -> HomeDividerViewHolder(parent.toBinding()) RECENT_V_GAME -> HomeRecentVGameViewHolder(parent.toBinding()) + DISCOVER_CARD -> HomeDiscoverCardViewHolder(parent.toBinding()) FOOTER_ITEM -> FooterViewHolder(mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false)) UNKNOWN_ITEM -> ReuseViewHolder(mLayoutInflater.inflate(R.layout.home_unknown_item, parent, false)) @@ -211,6 +220,7 @@ class HomeFragmentAdapter( is HomeDividerViewHolder -> holder.bindView(mDataList[position].lineDivider ?: 1F) is HomeGameCollectionViewHolder -> bindGameCollection(holder, position) is HomeRecentVGameViewHolder -> bindRecentVGame(holder, position) + is HomeDiscoverCardViewHolder -> bindDiscoverCard(holder, position) else -> mLegacyHomeFragmentAdapterAssistant.bindLegacyViewHolder(holder, mDataList[position], position) } @@ -326,6 +336,51 @@ class HomeFragmentAdapter( } } + private fun bindDiscoverCard(holder: HomeDiscoverCardViewHolder, position: Int) { + val homeItemData = mDataList[position] + val discoverCard = homeItemData.discoverCard!! + val binding = holder.binding + val exposureEventList = arrayListOf() + val listExposureEventList = arrayListOf() + val spanCount = 3 + + val snapHelper = holder.bindView(discoverCard, "首页") + + val exposureClosure: (Int) -> Unit = { + val gameList = discoverCard.games + runOnIoThread(true) { + listExposureEventList.clear() + + val startOffset = it * spanCount + val endOffset = if (startOffset + spanCount >= gameList.size) { + gameList.size + } else { + startOffset + spanCount + } + for (i in startOffset until endOffset) { + val event = ExposureEvent.createEventWithSourceConcat( + gameEntity = gameList[i].apply { + outerSequence = homeItemData.blockPosition + sequence = i + }, + basicSource = mBasicExposureSource, + source = listOf(ExposureSource("发现", gameList[i].getTypeName())) + ) + gameList[i].exposureEvent = event + listExposureEventList.add(event) + } + exposureEventList.addAll(listExposureEventList) + } + } + exposureClosure(0) + + binding.recyclerView.addOnScrollListener(OnPagerSnapScrollListener(snapHelper) { + exposureClosure(it) + }) + + homeItemData.exposureEventList = exposureEventList + } + private fun bindAttachGame(holder: HomeGameItemViewHolder, position: Int) { val homeItemData = mDataList[position] val attachGame = homeItemData.attachGame @@ -372,6 +427,7 @@ class HomeFragmentAdapter( || getItemViewType(gameAndPosition.position) == RECENT_V_GAME || getItemViewType(gameAndPosition.position) == ItemViewType.GAME_SUBJECT || getItemViewType(gameAndPosition.position) == ItemViewType.GAME_SUBJECT_SLIDE + || getItemViewType(gameAndPosition.position) == DISCOVER_CARD ) { val view = mLayoutManager.findViewByPosition(gameAndPosition.position) val recyclerView = view?.findViewById(R.id.recycler_view) @@ -383,6 +439,7 @@ class HomeFragmentAdapter( is GameHorizontalAdapter -> adapter.notifyItemByDownload(download) is GameHorizontalSlideAdapter -> adapter.notifyItemByDownload(download) is HomeRecentVGameAdapter -> adapter.notifyItemByDownload(download) + is DiscoverCardGameAdapter -> adapter.notifyItemByDownload(download) } } else { notifyItemChanged(gameAndPosition.position) @@ -407,6 +464,7 @@ class HomeFragmentAdapter( || getItemViewType(position) == ItemViewType.RANK_COLLECTION || getItemViewType(position) == ItemViewType.GAME_SUBJECT || getItemViewType(position) == ItemViewType.GAME_SUBJECT_SLIDE + || getItemViewType(position) == DISCOVER_CARD ) { val view = mLayoutManager.findViewByPosition(position) val recyclerView = view?.findViewById(R.id.recycler_view) @@ -415,6 +473,7 @@ class HomeFragmentAdapter( is GameVerticalAdapter -> adapter.notifyChildItem(packageName) is GameHorizontalAdapter -> adapter.notifyChildItem(packageName) is GameHorizontalSlideAdapter -> adapter.notifyChildItem(packageName) + is DiscoverCardGameAdapter -> adapter.notifyChildItem(packageName) else -> recyclerView?.adapter?.notifyDataSetChanged() } } else { @@ -456,6 +515,18 @@ class HomeFragmentAdapter( continue } + val discoverCard = itemData.discoverCard + if (discoverCard != null) { + discoverCard.games.forEach { + for (apkEntity in it.getApk()) { + if (apkEntity.packageName == packageName) { + positionList.add(GameAndPosition(it, position)) + } + } + } + continue + } + // 原游戏板块样式 mLegacyHomeFragmentAdapterAssistant.getLegacyGameEntityByPackage( positionList, @@ -486,5 +557,6 @@ class HomeFragmentAdapter( const val UNKNOWN_ITEM: Int = 111 const val GAME_COLLECTION_ITEM: Int = 116 const val RECENT_V_GAME: Int = 117 + const val DISCOVER_CARD: Int = 118 } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt b/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt index c928f6f710..0fa045ae28 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt @@ -1,9 +1,6 @@ package com.gh.gamecenter.home -import com.gh.gamecenter.entity.AmwayCommentEntity -import com.gh.gamecenter.entity.HomeContent -import com.gh.gamecenter.entity.HomeRecommend -import com.gh.gamecenter.entity.HomeSlide +import com.gh.gamecenter.entity.* import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData import com.gh.vspace.VGameItemData @@ -14,6 +11,7 @@ data class HomeItemData( var gameCollection: List? = null, var attachGame: HomeContent? = null, var recentVGame: ArrayList? = null, + var discoverCard: DiscoveryCardEntity? = null, var lineDivider: Float? = null, var unknownData: Any? = null ) : LegacyHomeItemData() \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt b/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt index 2f3b3a103a..3f7e1d98b1 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt @@ -18,6 +18,7 @@ import com.gh.gamecenter.common.baselist.LoadStatus import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.retrofit.BiResponse import com.gh.gamecenter.common.retrofit.Response +import com.gh.gamecenter.common.utils.observableToMain import com.gh.gamecenter.core.runOnIoThread import com.gh.gamecenter.core.utils.RandomUtils import com.gh.gamecenter.core.utils.SPUtils @@ -32,6 +33,7 @@ import com.gh.vspace.VHelper import com.halo.assistant.HaloApp import com.lightgame.download.DownloadEntity import com.lightgame.utils.Utils +import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import retrofit2.HttpException @@ -51,6 +53,8 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { private var mPluginList: List? = null // 插件化 private var mSmartSubject: SubjectEntity? = null // 智能推荐专题 private var mVGameList: ArrayList? = null // 最近的畅玩游戏 + private var mDiscoveryGameCard: DiscoveryGameCardEntity? = null + private var mDiscoveryGameCardLabels: List? = null private var mContentPage = 1 // 专题分页 private var mIsLoading = false @@ -292,6 +296,70 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { } } + private fun getDiscoverCardData() { + val discoveryGamesObservable = mApi.getDiscoveryGames(1, mapOf("refresh" to "true")) + val cardLabelsObservable = mApi.cardLabels + when { + mDiscoveryGameCard == null && mDiscoveryGameCardLabels == null -> { + Observable.mergeDelayError(cardLabelsObservable, discoveryGamesObservable) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onNext(response: Any) { + when (response) { + is ArrayList<*> -> mDiscoveryGameCardLabels = + response as ArrayList + is DiscoveryGameCardEntity -> mDiscoveryGameCard = + response.apply { games = ArrayList(games.take(18)) } + } + } + + override fun onComplete() { + filterDiscoveryLabel() + transformationItemData(mHomeContents) + } + }) + } + mDiscoveryGameCard == null -> { + discoveryGamesObservable.compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: DiscoveryGameCardEntity?) { + mDiscoveryGameCard = response?.apply { games = ArrayList(games.take(18)) } + filterDiscoveryLabel() + transformationItemData(mHomeContents) + } + }) + } + mDiscoveryGameCardLabels == null -> { + cardLabelsObservable.compose(observableToMain()) + .subscribe(object : Response>() { + override fun onResponse(response: ArrayList?) { + super.onResponse(response) + mDiscoveryGameCardLabels = response + filterDiscoveryLabel() + transformationItemData(mHomeContents) + } + }) + } + } + } + + // 过滤分组,现只需要"卡片一"分组 + private fun filterDiscoveryLabel() { + mDiscoveryGameCardLabels = mDiscoveryGameCardLabels?.filter { it.card == "卡片一" } + // 如果link_type为空需要在mGameTags中依次获取 + val gameTags = mDiscoveryGameCard?.gameTags + if (gameTags.isNullOrEmpty()) return + mDiscoveryGameCardLabels?.forEachIndexed { index, label -> + if (gameTags.size > index && label.type.isNullOrEmpty()) { + val gameTag = gameTags[index] + label.link = gameTag.link + label.type = gameTag.type + label.linkText = gameTag.linkText + label.title = gameTag.linkText + } + } + } + //换一批 private fun getChangeSubjectGame(subjectId: String) { mApi @@ -599,6 +667,22 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { } mSnapshotItemList.add(LegacyHomeSubjectTransformer.getBlankSpacingItem(HomeItemData()) as HomeItemData) mSnapshotItemList.add(homeItemData) + } else if (linkType == "explore") { + if (mDiscoveryGameCard == null || mDiscoveryGameCardLabels == null) { + getDiscoverCardData() + } else { + val homeItemData = HomeItemData().apply { + blockPosition = i + discoverCard = DiscoveryCardEntity( + mDiscoveryGameCard?.games ?: arrayListOf(), + mDiscoveryGameCardLabels ?: listOf() + ) + } + mSnapshotItemList.add(homeItemData) + mDiscoveryGameCard?.games?.forEach { + addGamePositionAndPackage(it) + } + } } else { val unknown = HomeItemData() unknown.blockPosition = i + 1 diff --git a/app/src/main/java/com/gh/gamecenter/home/discovercard/DiscoverCardGameAdapter.kt b/app/src/main/java/com/gh/gamecenter/home/discovercard/DiscoverCardGameAdapter.kt new file mode 100644 index 0000000000..51027dc550 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/discovercard/DiscoverCardGameAdapter.kt @@ -0,0 +1,227 @@ +package com.gh.gamecenter.home.discovercard + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.RelativeLayout +import android.widget.TextView +import com.gh.common.util.DownloadItemUtils +import com.gh.gamecenter.GameDetailActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.common.constant.Constants +import com.gh.gamecenter.common.retrofit.BiResponse +import com.gh.gamecenter.common.utils.* +import com.gh.gamecenter.core.utils.StringUtils +import com.gh.gamecenter.core.utils.ToastUtils +import com.gh.gamecenter.databinding.LayoutPopupDiscoveryDislikeBinding +import com.gh.gamecenter.discovery.DiscoveryAdapter.DiscoveryGameViewHolder +import com.gh.gamecenter.entity.GameEntity +import com.gh.gamecenter.retrofit.RetrofitManager +import com.google.android.flexbox.FlexboxLayout +import com.lightgame.adapter.BaseRecyclerAdapter +import com.lightgame.download.DownloadEntity +import okhttp3.ResponseBody + +class DiscoverCardGameAdapter( + context: Context, + private var mEntrance: String, + private var mList: ArrayList +) : BaseRecyclerAdapter(context) { + + private var countAndKey: Pair? = null + + init { + var dataIds = "" + mList.forEach { + dataIds += it.id + } + if (dataIds.isNotEmpty()) countAndKey = Pair(mList.size, dataIds) + } + + override fun getItemCount() = mList.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiscoveryGameViewHolder { + return DiscoveryGameViewHolder(parent.toBinding()) + } + + override fun onBindViewHolder(holder: DiscoveryGameViewHolder, position: Int) { + val gameEntity = mList[position] + val isEndOfRow = position >= if (itemCount % SPAN_COUNT == 0) { + itemCount - SPAN_COUNT + } else { + itemCount - itemCount % SPAN_COUNT + } + val screenWidth = mContext.resources.displayMetrics.widthPixels + if (!isEndOfRow) { + holder.itemView.layoutParams = ViewGroup.LayoutParams(screenWidth - 58F.dip2px(), 76F.dip2px()) + holder.itemView.setPadding(14F.dip2px(), 0, 0, 0) + } else { + holder.itemView.layoutParams = ViewGroup.LayoutParams(screenWidth - 44F.dip2px(), 76F.dip2px()) + holder.itemView.setPadding(14F.dip2px(), 0, 14F.dip2px(), 0) + } + holder.itemView.setBackgroundResource(R.color.transparent.toColor(mContext)) + DownloadItemUtils.updateItem(mContext, gameEntity, holder, true, "star") + holder.bindGameItem(gameEntity) + holder.itemView.setOnClickListener { + GameDetailActivity.startGameDetailActivity( + mContext, + gameEntity, + StringUtils.buildString("(${mEntrance}", "-发现页卡片[", (position).toString(), "])"), + traceEvent = gameEntity.exposureEvent + ) + } + holder.itemView.setOnLongClickListener { + showDislikeWindow(holder.itemView, position, gameEntity) + true + } + DownloadItemUtils.setOnClickListener( + mContext, + holder.binding.downloadBtn, + gameEntity, + position, + this, + StringUtils.buildString("(${mEntrance}", "-发现页卡片[", (position).toString(), "])"), + StringUtils.buildString(mEntrance, ":", gameEntity.name), + gameEntity.exposureEvent + ) + } + + @SuppressLint("CheckResult") + private fun showDislikeWindow(view: View, position: Int, gameEntity: GameEntity) { + val decorView = (mContext as Activity).window.decorView as? FrameLayout + val binding = LayoutPopupDiscoveryDislikeBinding.inflate(LayoutInflater.from(mContext), decorView, true) + binding.reasonFlex.removeAllViews() + Constants.FEEDBACK_REASON_LIST.toList().forEach { + val popupItem = getPopupItem(it) + val params = + FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + binding.reasonFlex.addView(popupItem, params) + popupItem.setOnClickListener { + discoveryFeedback(gameEntity.id, popupItem.text.toString(), gameEntity.type ?: "") { + mList.removeAt(position) + notifyItemRemoved(position) + decorView?.removeView(binding.root) + ToastUtils.showToast("已根据你的偏好优化推荐机制~", Gravity.CENTER) + } + } + } + binding.root.setOnClickListener { + decorView?.removeView(it) + } + binding.contentView.visibility = View.INVISIBLE + binding.contentView.post { + val (windowPosition, isNeedShowUp) = getWindowPosition(view, binding.contentView, 36F.dip2px()) + (binding.contentView.layoutParams as RelativeLayout.LayoutParams).run { + topMargin = windowPosition[1] + binding.contentView.layoutParams = this + } + binding.anchorUpIv.goneIf(isNeedShowUp) + binding.anchorDownIv.goneIf(!isNeedShowUp) + binding.contentView.visibility = View.VISIBLE + } + } + + private fun getPopupItem(reason: String): TextView { + return TextView(mContext).apply { + height = 32F.dip2px() + text = reason + textSize = 12F + includeFontPadding = false + setTextColor(R.color.text_subtitle.toColor(mContext)) + gravity = Gravity.CENTER + setPadding(12F.dip2px(), 0F.dip2px(), 12F.dip2px(), 0F.dip2px()) + background = R.drawable.bg_shape_space_radius_8.toDrawable(mContext) + } + } + + private fun getWindowPosition( + anchorView: View, + contentView: View, + distanceY: Int = 0 + ): Pair { + val windowPos = IntArray(2) + val anchorLoc = IntArray(2) + anchorView.getLocationInWindow(anchorLoc) + val anchorHeight = anchorView.height + val screenHeight = anchorView.context.resources.displayMetrics.heightPixels + contentView.measure(0, 0) + val contentViewWidth = contentView.width + val contentViewHeight = contentView.height + val isNeedShowUp = screenHeight - anchorLoc[1] - anchorHeight < contentViewHeight + windowPos[1] = if (isNeedShowUp) { + anchorLoc[1] - contentViewHeight + distanceY + } else { + anchorLoc[1] + anchorHeight - distanceY + } + windowPos[0] = (anchorLoc[0] - contentViewWidth + anchorView.width) / 2 + return Pair(windowPos, isNeedShowUp) + } + + @SuppressLint("CheckResult") + private fun discoveryFeedback(gameId: String, reason: String, type: String, callback: () -> Unit) { + val paramsMap = mapOf( + "reason" to reason, + "type" to type + ) + RetrofitManager.getInstance().api.discorveryFeedback(gameId, paramsMap.toRequestBody()) + .compose(singleToMain()) + .subscribe(object : BiResponse() { + + override fun onSuccess(data: ResponseBody) { + callback.invoke() + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + ToastUtils.showToast("反馈失败") + } + }) + } + + fun notifyItemByDownload(downloadEntity: DownloadEntity?) { + if (downloadEntity == null) { + notifyDataSetChanged() + } else { + mList.forEachIndexed { position, gameEntity -> + if (downloadEntity.gameId == gameEntity.id) { + notifyItemChanged(position) + } + } + } + } + + fun notifyChildItem(packageName: String) { + mList.forEachIndexed { position, gameEntity -> + gameEntity.getApk().forEach { apkEntity -> + if (apkEntity.packageName == packageName) { + notifyItemChanged(position) + return + } + } + } + } + + fun checkResetData(update: ArrayList) { + var dataIds = "" + update.forEach { dataIds += it.id } + + mList = update + if (countAndKey?.first == update.size && countAndKey?.second != dataIds) { // 数量不变,内容发生改变 + notifyItemRangeChanged(0, itemCount) + } else if (countAndKey?.first != update.size) { // 数量发生改变 + notifyDataSetChanged() + } + + // 重新刷新数据标识 + countAndKey = Pair(update.size, dataIds) + } + + companion object { + const val SPAN_COUNT = 3 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/discovercard/HomeDiscoverCardViewHolder.kt b/app/src/main/java/com/gh/gamecenter/home/discovercard/HomeDiscoverCardViewHolder.kt new file mode 100644 index 0000000000..ffa5034053 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/discovercard/HomeDiscoverCardViewHolder.kt @@ -0,0 +1,108 @@ +package com.gh.gamecenter.home.discovercard + +import android.content.Context +import android.view.Gravity +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.Config +import com.gh.common.util.DirectUtils +import com.gh.common.util.NewFlatLogUtils +import com.gh.gamecenter.R +import com.gh.gamecenter.common.base.BaseRecyclerViewHolder +import com.gh.gamecenter.common.utils.dip2px +import com.gh.gamecenter.common.utils.goneIf +import com.gh.gamecenter.common.utils.setDrawableEnd +import com.gh.gamecenter.common.utils.toColor +import com.gh.gamecenter.databinding.HomeDiscoverCardItemBinding +import com.gh.gamecenter.discovery.DiscoveryActivity +import com.gh.gamecenter.discovery.interestedgame.InterestedGameActivity +import com.gh.gamecenter.entity.DiscoveryCardEntity +import com.gh.gamecenter.entity.DiscoveryGameCardLabel +import com.gh.gamecenter.game.vertical.SpanCountPagerSnapHelper +import splitties.views.dsl.core.endMargin + +class HomeDiscoverCardViewHolder(val binding: HomeDiscoverCardItemBinding) : BaseRecyclerViewHolder(binding.root) { + + fun bindView( + entity: DiscoveryCardEntity, + entrance: String, blockName: String = "" + ): SpanCountPagerSnapHelper { + val spanCount = 3 + val snapHelper = SpanCountPagerSnapHelper(spanCount, true) + binding.run { + val context = root.context + prefsIv.goneIf(!Config.getUserInterestedGame()) { + prefsIv.setOnClickListener { + context.startActivity(InterestedGameActivity.getIntent(context, "${entrance}-发现页卡片")) + NewFlatLogUtils.logInterestedGameClick(entrance, blockName) + } + } + allTv.setOnClickListener { + context.startActivity(DiscoveryActivity.getIntent(context, "${entrance}-发现页卡片")) + NewFlatLogUtils.logDiscoverDetailPageEnter(entrance, blockName) + } + recyclerView.run { + clearOnScrollListeners() + (itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false + onFlingListener = null + + if (layoutManager !is GridLayoutManager) { + layoutManager = GridLayoutManager(context, spanCount, RecyclerView.HORIZONTAL, false) + } + + if (adapter is DiscoverCardGameAdapter) { + val discoverCardGameAdapter = adapter as DiscoverCardGameAdapter + discoverCardGameAdapter.checkResetData(entity.games) + } else { + adapter = DiscoverCardGameAdapter(context, entrance, entity.games) + } + snapHelper.attachToRecyclerView(this) + isNestedScrollingEnabled = false + } + + val childCount = labelContainer.childCount + entity.label.forEachIndexed { index, label -> + if (index < childCount) { + val labelView = labelContainer.getChildAt(index) as TextView + updateLabelView(context, labelView, label, index == entity.label.size - 1) + } else { + val newLabelView = TextView(context) + updateLabelView(context, newLabelView, label, index == entity.label.size - 1) + labelContainer.addView(newLabelView) + } + } + } + return snapHelper + } + + private fun updateLabelView( + context: Context, + textView: TextView, + label: DiscoveryGameCardLabel, + isLast: Boolean + ) { + if (textView.text == label.title) return + + textView.run { + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + 36F.dip2px() + ).apply { endMargin = if (isLast) 14F.dip2px() else 8F.dip2px() } + setPadding(12F.dip2px(), 0, 12F.dip2px(), 0) + setBackgroundResource(R.drawable.bg_shape_space_radius_4) + gravity = Gravity.CENTER + setDrawableEnd(R.drawable.ic_interest_arrow) + compoundDrawablePadding = 8F.dip2px() + setTextColor(R.color.text_subtitle.toColor(context)) + textSize = 12F + text = label.title + label.text = label.linkText + setOnClickListener { + DirectUtils.directToLinkPage(context, label, "发现页卡片", "") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java index 58a56dfb17..e7c67f145e 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java +++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java @@ -50,6 +50,7 @@ import com.gh.gamecenter.entity.HelpEntity; import com.gh.gamecenter.entity.HomeContent; import com.gh.gamecenter.entity.HomeDataEntity; import com.gh.gamecenter.entity.ImageInfoEntity; +import com.gh.gamecenter.entity.InterestedGameEntity; import com.gh.gamecenter.entity.LibaoDetailEntity; import com.gh.gamecenter.entity.LibaoEntity; import com.gh.gamecenter.entity.LibaoStatusEntity; @@ -3106,4 +3107,15 @@ public interface ApiService { @POST("games/archives/configs") Observable> getGamesArchiveConfigs(@Body RequestBody body); + /** + * 获取偏好设置(分类标签)的配置数据及用户状态 + */ + @GET("user/_interested_game") + Single getInterestedGame(); + + /** + * 更新用户偏好设置 + */ + @PUT("user/_interested_game") + Single postInterestedGame(@Body RequestBody body); } \ No newline at end of file diff --git a/app/src/main/res/color/interested_game_tag_text_selector.xml b/app/src/main/res/color/interested_game_tag_text_selector.xml new file mode 100644 index 0000000000..c838ad7d30 --- /dev/null +++ b/app/src/main/res/color/interested_game_tag_text_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-night-xxxhdpi/pic_discover_card_title.webp b/app/src/main/res/drawable-night-xxxhdpi/pic_discover_card_title.webp new file mode 100644 index 0000000000..b196bbb91a Binary files /dev/null and b/app/src/main/res/drawable-night-xxxhdpi/pic_discover_card_title.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_discover_card_prefs.webp b/app/src/main/res/drawable-xxxhdpi/ic_discover_card_prefs.webp new file mode 100644 index 0000000000..6c44f69a7c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_discover_card_prefs.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_interested_game_add.png b/app/src/main/res/drawable-xxxhdpi/ic_interested_game_add.png new file mode 100644 index 0000000000..b9a5d24a18 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_interested_game_add.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_discover_card_title.webp b/app/src/main/res/drawable-xxxhdpi/pic_discover_card_title.webp new file mode 100644 index 0000000000..5314e9709b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_discover_card_title.webp differ diff --git a/app/src/main/res/drawable/bg_discover_card.xml b/app/src/main/res/drawable/bg_discover_card.xml new file mode 100644 index 0000000000..540a81c3f2 --- /dev/null +++ b/app/src/main/res/drawable/bg_discover_card.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_shape_2496ff_alpha_10_radius_8.xml b/app/src/main/res/drawable/bg_shape_2496ff_alpha_10_radius_8.xml new file mode 100644 index 0000000000..1c1b2c9c97 --- /dev/null +++ b/app/src/main/res/drawable/bg_shape_2496ff_alpha_10_radius_8.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/border_round_transparent_stroke_divider.xml b/app/src/main/res/drawable/border_round_transparent_stroke_divider.xml new file mode 100644 index 0000000000..dbc21ae1f3 --- /dev/null +++ b/app/src/main/res/drawable/border_round_transparent_stroke_divider.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_interested_game_tag_delete.xml b/app/src/main/res/drawable/ic_interested_game_tag_delete.xml new file mode 100644 index 0000000000..eab80245fe --- /dev/null +++ b/app/src/main/res/drawable/ic_interested_game_tag_delete.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/interested_game_tag_selector.xml b/app/src/main/res/drawable/interested_game_tag_selector.xml new file mode 100644 index 0000000000..9f5b239710 --- /dev/null +++ b/app/src/main/res/drawable/interested_game_tag_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_interested_game_type.xml b/app/src/main/res/layout/dialog_interested_game_type.xml new file mode 100644 index 0000000000..4dc401718b --- /dev/null +++ b/app/src/main/res/layout/dialog_interested_game_type.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_interested_game.xml b/app/src/main/res/layout/fragment_interested_game.xml new file mode 100644 index 0000000000..07a5612d96 --- /dev/null +++ b/app/src/main/res/layout/fragment_interested_game.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/home_discover_card_item.xml b/app/src/main/res/layout/home_discover_card_item.xml new file mode 100644 index 0000000000..b6ddf9dd8b --- /dev/null +++ b/app/src/main/res/layout/home_discover_card_item.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/interested_game_sub_tag_item.xml b/app/src/main/res/layout/interested_game_sub_tag_item.xml new file mode 100644 index 0000000000..56bbdbeba9 --- /dev/null +++ b/app/src/main/res/layout/interested_game_sub_tag_item.xml @@ -0,0 +1,15 @@ + + diff --git a/app/src/main/res/layout/interested_game_type_item.xml b/app/src/main/res/layout/interested_game_type_item.xml new file mode 100644 index 0000000000..a0f0402305 --- /dev/null +++ b/app/src/main/res/layout/interested_game_type_item.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recommend_interest_footer.xml b/app/src/main/res/layout/item_recommend_interest_footer.xml index d3c03a261e..8b074b29c2 100644 --- a/app/src/main/res/layout/item_recommend_interest_footer.xml +++ b/app/src/main/res/layout/item_recommend_interest_footer.xml @@ -6,7 +6,20 @@ android:background="@color/background_white" android:padding="16dp"> + + + app:layout_constraintTop_toBottomOf="@+id/interestedGameTv" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aaaad60087..632c055f07 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -483,6 +483,8 @@ 安装游戏 使用云存档后,将会覆盖您畅玩助手内的相关游戏进度,若您想保留自己的游戏数据,请先上传您当前的游戏存档后再使用 + 设置偏好]]> ,让推荐更懂你的心~ + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1119427cec..e52044a8cc 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -279,4 +279,34 @@ @drawable/button_round_gray_light @color/text_subtitle + + + + + + \ No newline at end of file diff --git a/module_common/src/main/java/com/gh/gamecenter/common/constant/ItemViewType.java b/module_common/src/main/java/com/gh/gamecenter/common/constant/ItemViewType.java index df954ee2bf..3ef86f01b3 100644 --- a/module_common/src/main/java/com/gh/gamecenter/common/constant/ItemViewType.java +++ b/module_common/src/main/java/com/gh/gamecenter/common/constant/ItemViewType.java @@ -44,6 +44,7 @@ public class ItemViewType { public static final int COMMON_LINK_COLLECTION12 = 35; // 通用链接合集(1-2样式) public static final int GAME_ITEM = 36; public static final int DIVIDER_ITEM = 37; + public static final int DISCOVER_CARD = 38; /** * 普通列表 diff --git a/module_common/src/main/res/drawable/background_shape_white_radius_6_bottom_only.xml b/module_common/src/main/res/drawable/background_shape_white_radius_6_bottom_only.xml new file mode 100644 index 0000000000..4e61dbb44a --- /dev/null +++ b/module_common/src/main/res/drawable/background_shape_white_radius_6_bottom_only.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/module_common/src/main/res/drawable/background_shape_white_radius_6_top_only.xml b/module_common/src/main/res/drawable/background_shape_white_radius_6_top_only.xml new file mode 100644 index 0000000000..3160a50005 --- /dev/null +++ b/module_common/src/main/res/drawable/background_shape_white_radius_6_top_only.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file