diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e1e996d8c3..08591511b7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -764,6 +764,10 @@ android:name=".qa.editor.InsertGameCollectionWrapperActivity" android:screenOrientation="portrait" /> + + directToVideoTab(context) - "toolkit" -> context.startActivity(ToolBoxActivity.getIntent(context, entrance)) + "toolkit" -> context.startActivity(ToolBoxBlockActivity.getIntent(context, entrance)) "column_test" -> context.startActivity( GameServerTestActivity.getIntent( diff --git a/app/src/main/java/com/gh/common/util/EnvHelper.kt b/app/src/main/java/com/gh/common/util/EnvHelper.kt index d097d187a9..53200a4c73 100644 --- a/app/src/main/java/com/gh/common/util/EnvHelper.kt +++ b/app/src/main/java/com/gh/common/util/EnvHelper.kt @@ -24,6 +24,19 @@ object EnvHelper { } } + @JvmStatic + fun getNewHost(): String { + return if (!isTestEnv()) { + Constants.NEW_API_HOST + } else { + if (isDevEnv) { + Constants.NEW_DEV_API_HOST + } else { + Constants.NEW_API_HOST + } + } + } + // 包体是否为测试包 @JvmStatic fun isTestEnv(): Boolean { diff --git a/app/src/main/java/com/gh/gamecenter/entity/ToolBoxBlockEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/ToolBoxBlockEntity.kt new file mode 100644 index 0000000000..7ea04d3c83 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/ToolBoxBlockEntity.kt @@ -0,0 +1,16 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class ToolBoxBlockEntity( + @SerializedName("_id") + val categoryId: String = "", + @SerializedName("name") + val categoryName: String = "", + val total: Int = 0, + @SerializedName("data") + val toolboxList: List = ArrayList() +) : Parcelable diff --git a/app/src/main/java/com/gh/gamecenter/entity/ToolBoxEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/ToolBoxEntity.kt index 72bce0d2b8..f878e1dd29 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/ToolBoxEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/ToolBoxEntity.kt @@ -23,6 +23,9 @@ class ToolBoxEntity : Parcelable { var time: Long = 0 + // 用户上次打开使用时间(仅用于历史记录) + var lastOpenTime: Long = 0 + @SerializedName("me") var me: MeEntity? = null @@ -41,6 +44,34 @@ class ToolBoxEntity : Parcelable { dest.writeParcelable(this.me, flags) } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ToolBoxEntity + + if (id != other.id) return false + if (icon != other.icon) return false + if (name != other.name) return false + if (des != other.des) return false + if (url != other.url) return false + if (time != other.time) return false + if (me != other.me) return false + + return true + } + + override fun hashCode(): Int { + var result = id?.hashCode() ?: 0 + result = 31 * result + (icon?.hashCode() ?: 0) + result = 31 * result + (name?.hashCode() ?: 0) + result = 31 * result + (des?.hashCode() ?: 0) + result = 31 * result + (url?.hashCode() ?: 0) + result = 31 * result + time.hashCode() + result = 31 * result + (me?.hashCode() ?: 0) + return result + } + constructor() protected constructor(`in`: Parcel) { diff --git a/app/src/main/java/com/gh/gamecenter/forum/home/WelfaresAdapter.kt b/app/src/main/java/com/gh/gamecenter/forum/home/WelfaresAdapter.kt index adff595420..7e309808f2 100644 --- a/app/src/main/java/com/gh/gamecenter/forum/home/WelfaresAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/forum/home/WelfaresAdapter.kt @@ -7,6 +7,7 @@ import com.gh.base.BaseRecyclerViewHolder import com.gh.common.util.* import com.gh.gamecenter.* import com.gh.gamecenter.databinding.ForumWelfareItemBinding +import com.gh.gamecenter.toolbox.ToolBoxBlockActivity import com.lightgame.adapter.BaseRecyclerAdapter class WelfaresAdapter(context: Context, @@ -31,7 +32,7 @@ class WelfaresAdapter(context: Context, when (entity.second) { "工具箱" -> { NewLogUtils.logForumPageEvent("click_forum_toolbox") - mContext.startActivity(ToolBoxActivity.getIntent(mContext, "(社区-论坛:工具箱)")) + mContext.startActivity(ToolBoxBlockActivity.getIntent(mContext, "(社区-论坛:工具箱)")) } "礼包中心" -> { diff --git a/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt b/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt index 1853a5c43e..23ebc78a4c 100644 --- a/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt @@ -28,6 +28,7 @@ import com.gh.gamecenter.qa.myqa.MyAskActivity import com.gh.gamecenter.security.SecurityActivity import com.gh.gamecenter.simulatorgame.SimulatorGameActivity import com.gh.gamecenter.teenagermode.TeenagerModeActivity +import com.gh.gamecenter.toolbox.ToolBoxBlockActivity import com.gh.gamecenter.user.UserViewModel import com.gh.gamecenter.video.videomanager.VideoManagerActivity import com.halo.assistant.HaloApp @@ -248,7 +249,7 @@ class PersonalFunctionAdapter(val context: Context, val groupName: String, var m "工具箱" -> { DataCollectionUtils.uploadClick(context, "工具箱", "发现") - context.startActivity(ToolBoxActivity.getIntent(context, "(发现:工具箱)")) + context.startActivity(ToolBoxBlockActivity.getIntent(context, "(发现:工具箱)")) } "安装包清理" -> { DataCollectionUtils.uploadClick(context, "安装包清理", "发现") diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/RetrofitManager.java b/app/src/main/java/com/gh/gamecenter/retrofit/RetrofitManager.java index 9c24c9ca8d..b6865dd421 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/RetrofitManager.java +++ b/app/src/main/java/com/gh/gamecenter/retrofit/RetrofitManager.java @@ -31,6 +31,7 @@ public class RetrofitManager { private static final int UPLOAD_CALL_TIME_OUT = 20; // 图片上传超时时间 private static final byte[] LOCK = new byte[0]; private final ApiService mApiService; + private final ApiService mNewApiService; private final ApiService mUploadApiService; public static T provideService(OkHttpClient client, String url, Class serviceCls) { @@ -45,6 +46,7 @@ public class RetrofitManager { Context context = HaloApp.getInstance().getApplicationContext(); OkHttpClient okHttpNormalConfig = getOkHttpConfig(context, 0, 2); mApiService = provideService(okHttpNormalConfig, Config.API_HOST, ApiService.class); + mNewApiService = provideService(okHttpNormalConfig, Config.NEW_API_HOST, ApiService.class); mUploadApiService = provideService(getOkHttpConfig(context, UPLOAD_CALL_TIME_OUT, 1), Config.API_HOST, ApiService.class); } @@ -72,6 +74,10 @@ public class RetrofitManager { return mApiService; } + public ApiService getNewApi() { + return mNewApiService; + } + public ApiService getUploadApi() { return mUploadApiService; } 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 8fc6bb8c5d..82e662810b 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 @@ -356,6 +356,12 @@ public interface ApiService { @POST("games/platform_requests/{platform_id}:vote") Observable postVersionVote(@Path("platform_id") String platformId); + /** + * 获取工具箱分类数据 + */ + @GET("toolkit") + Observable> getCategoryToolKits(); + /** * 根据game_id获取工具箱数据 */ diff --git a/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxBlockActivity.kt b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxBlockActivity.kt new file mode 100644 index 0000000000..42dec2b571 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxBlockActivity.kt @@ -0,0 +1,246 @@ +package com.gh.gamecenter.toolbox + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.KeyEvent +import android.view.MotionEvent +import android.view.View +import android.view.View.OnFocusChangeListener +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import android.widget.TextView +import androidx.activity.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.gh.base.ToolBarActivity +import com.gh.common.util.* +import com.gh.common.view.CustomLinkMovementMethod +import com.gh.gamecenter.R +import com.gh.gamecenter.SuggestionActivity +import com.gh.gamecenter.baselist.LoadStatus +import com.gh.gamecenter.databinding.ActivityToolboxBlockBinding +import com.gh.gamecenter.suggest.SuggestType +import com.google.android.material.appbar.AppBarLayout +import com.lightgame.utils.Util_System_Keyboard +import com.lightgame.utils.Utils + + +class ToolBoxBlockActivity : ToolBarActivity() { + + private val mViewModel: ToolBoxViewModel by viewModels() + private lateinit var mBinding: ActivityToolboxBlockBinding + private lateinit var mAdapter: ToolBoxBlockAdapter + private lateinit var mSearchAdapter: ToolBoxItemAdapter + private lateinit var mLayoutManager: LinearLayoutManager + + private var mIsSearch = false // 记录页面状态 搜索页面/普通页面 + + override fun getLayoutId() = R.layout.activity_toolbox_block + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mBinding = ActivityToolboxBlockBinding.bind(mContentView) + mAdapter = ToolBoxBlockAdapter(this) + mSearchAdapter = ToolBoxItemAdapter(this, false, mViewModel) + mLayoutManager = LinearLayoutManager(this@ToolBoxBlockActivity) + setNavigationTitle("光环工具箱") + mBinding.run { + toolboxRefresh.setColorSchemeResources(R.color.theme) + toolboxRefresh.setOnRefreshListener { + refresh() + } + feedbackTv.text = SpanBuilder("需要其他工具,点击反馈").click(7, 11, R.color.theme, false) { + SuggestionActivity.startSuggestionActivity( + this@ToolBoxBlockActivity, + SuggestType.normal, + null, + null + ) + }.build() + feedbackTv.movementMethod = CustomLinkMovementMethod.getInstance() + closeIv.setOnClickListener { mBinding.feedbackCv.visibility = View.GONE } + toolboxRv.adapter = mAdapter + toolboxRv.layoutManager = mLayoutManager + toolboxAppbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> + toolboxRefresh.isEnabled = verticalOffset == 0 + }) + toolboxRv.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (newState == RecyclerView.SCROLL_STATE_IDLE && mLayoutManager.findLastVisibleItemPosition() == mSearchAdapter.itemCount - 1 && mIsSearch && mViewModel.searchStatus.value == LoadStatus.LIST_LOADED) { + mViewModel.getSearchResultList() + } + } + }) + } + mViewModel.toolBoxBlockList.observeNonNull(this, { list -> + mAdapter.setDataList(list) + }) + mViewModel.searchResultList.observeNonNull(this, { list -> + mSearchAdapter.setDataList(list) + }) + mViewModel.loadStatus.observeNonNull(this, { + when (it) { + LoadStatus.INIT_LOADED -> loadDone() + LoadStatus.INIT_EMPTY -> loadEmpty() + LoadStatus.INIT_FAILED -> loadError() + } + }) + mViewModel.searchStatus.observeNonNull(this, { + when (it) { + LoadStatus.INIT_LOADED -> loadDone() + LoadStatus.INIT_EMPTY -> loadEmpty() + LoadStatus.INIT_FAILED -> loadError() + LoadStatus.INIT_OVER, + LoadStatus.LIST_LOADED, + LoadStatus.LIST_FAILED, + LoadStatus.LIST_OVER -> { + loadDone() + mSearchAdapter.notifyItemChanged(mSearchAdapter.itemCount - 1) + } + } + }) + + initSearch() + } + + override fun onResume() { + super.onResume() + if (!mIsSearch) mViewModel.getToolBoxList() + } + + private fun loadDone() { + mBinding.toolboxRv.visibility = View.VISIBLE + mBinding.toolboxRefresh.isRefreshing = false + mBinding.reuseNoneData.root.visibility = View.GONE + mBinding.reuseNoConnection.root.visibility = View.GONE + mBinding.reuseLoading.root.visibility = View.GONE + } + + private fun loadError() { + mBinding.toolboxRv.visibility = View.GONE + mBinding.toolboxRefresh.isRefreshing = false + mBinding.reuseNoneData.root.visibility = View.GONE + mBinding.reuseNoConnection.root.visibility = View.VISIBLE + mBinding.reuseLoading.root.visibility = View.GONE + mBinding.reuseNoConnection.root.setOnClickListener { + refresh() + mBinding.reuseNoConnection.root.visibility = View.GONE + mBinding.reuseLoading.root.visibility = View.VISIBLE + } + } + + private fun loadEmpty() { + mBinding.toolboxRv.visibility = View.GONE + mBinding.toolboxRefresh.isRefreshing = false + mBinding.reuseNoneData.root.visibility = View.VISIBLE + mBinding.reuseNoConnection.root.visibility = View.GONE + mBinding.reuseLoading.root.visibility = View.GONE + mBinding.reuseNoneData.reuseTvNoneData.text = + if (mIsSearch) "未找到结果,点我反馈" else resources.getString(R.string.game_empty) + mBinding.reuseNoneData.root.setOnClickListener { + if (mIsSearch) SuggestionActivity.startSuggestionActivity( + this, + SuggestType.functionSuggest, + null, + null + ) + } + } + + private fun refresh() { + if (mIsSearch) { + mViewModel.getSearchResultList(true) + } else { + mViewModel.getToolBoxList() + } + } + + private fun search(isSearch: Boolean, searchKey: String) { + if (mBinding.reuseNoneData.root.visibility == View.VISIBLE) { + mBinding.reuseNoneData.root.visibility = View.GONE + } + mIsSearch = isSearch + mViewModel.mSearchKey = searchKey + changeAdapter(isSearch) + if (isSearch) { + mBinding.reuseLoading.root.visibility = View.VISIBLE + mBinding.toolboxRv.visibility = View.GONE + mViewModel.getSearchResultList(true) + } + } + + private fun initSearch() { + val backTv = mBinding.reuseSearchBar.tvBack + val searchTv = mBinding.reuseSearchBar.tvSearch + val searchEt = mBinding.reuseSearchBar.etSearch + mBinding.reuseSearchBar.root.setPadding(16F.dip2px(), 8F.dip2px(), 16F.dip2px(), 8F.dip2px()) + backTv.setOnClickListener { + search(false, "") + searchEt.text.clear() + } + TextHelper.limitTheLengthOfEditText(searchEt, 20, + object : TextHelper.ExceedTextLengthLimitCallback { + override fun onExceed() { + toast("最多输入20字") + } + }) + searchTv.setOnClickListener { + if (searchEt.text.toString().isEmpty()) { + Utils.toast(this, R.string.search_hint) + return@setOnClickListener + } + Util_System_Keyboard.hideSoftKeyboard(this, searchEt) + search(true, searchEt.text.toString()) + } + searchEt.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean -> + if (!hasFocus) { + Util_System_Keyboard.hideSoftKeyboard(this, searchEt) + } + } + searchEt.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? -> + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + searchTv.performClick() + } + false + } + } + + private fun changeAdapter(isSearch: Boolean) { + mBinding.toolboxRv.adapter = if (isSearch) mSearchAdapter else mAdapter + mBinding.reuseSearchBar.tvBack.visibility = if (mIsSearch) View.VISIBLE else View.GONE + } + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + if (ev?.action == MotionEvent.ACTION_DOWN) { + val v = currentFocus + if (isShouldHideKeyboard(v, ev)) { + Util_System_Keyboard.hideSoftKeyboard(this) + } + } + return super.dispatchTouchEvent(ev) + } + + private fun isShouldHideKeyboard(v: View?, event: MotionEvent): Boolean { + if (v is EditText) { + val l = intArrayOf(0, 0) + v.getLocationInWindow(l) + val left = l[0] + val top = l[1] + val bottom = top + v.getHeight() + val right = left + v.getWidth() + return (event.x <= left || event.x >= right || event.y <= top || event.y >= bottom) + } + return false + } + + companion object { + @JvmStatic + fun getIntent(context: Context, entrance: String?): Intent { + val intent = Intent(context, ToolBoxBlockActivity::class.java) + intent.putExtra(EntranceUtils.KEY_ENTRANCE, entrance) + return intent + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxBlockAdapter.kt b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxBlockAdapter.kt new file mode 100644 index 0000000000..eaef153009 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxBlockAdapter.kt @@ -0,0 +1,97 @@ +package com.gh.gamecenter.toolbox + +import android.content.Context +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.GridLayoutManager +import com.gh.base.BaseRecyclerViewHolder +import com.gh.common.util.goneIf +import com.gh.common.util.safelyGetInRelease +import com.gh.gamecenter.baselist.ListExecutor +import com.gh.gamecenter.databinding.ToolboxBlockItemBinding +import com.gh.gamecenter.entity.ToolBoxBlockEntity +import com.lightgame.adapter.BaseRecyclerAdapter +import java.util.* + +class ToolBoxBlockAdapter(context: Context) : BaseRecyclerAdapter(context) { + + private var mEntityList = listOf() + + fun setDataList(dataList: List) { + if (dataList.isEmpty() || mEntityList.size > dataList.size) { + mEntityList = dataList + notifyDataSetChanged() + return + } + ListExecutor.workerExecutor.execute { + val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int { + return mEntityList.size + } + + override fun getNewListSize(): Int { + return dataList.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = mEntityList.safelyGetInRelease(oldItemPosition) + val newItem = dataList.safelyGetInRelease(newItemPosition) + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItemPosition: Int, + newItemPosition: Int + ): Boolean { + val oldItem = mEntityList.safelyGetInRelease(oldItemPosition) + val newItem = dataList.safelyGetInRelease(newItemPosition) + return oldItem == newItem + } + }) + ListExecutor.uiExecutor.execute { + mEntityList = ArrayList(dataList) + diffResult.dispatchUpdatesTo(this) + } + } + } + + override fun onBindViewHolder(holder: ToolBoxBlockViewHolder, position: Int) { + holder.bindToolBoxBlock(mEntityList[position]) + holder.binding.divider.goneIf(position == itemCount - 1 || mEntityList[position].categoryName == "最近使用") + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + ToolBoxBlockViewHolder(ToolboxBlockItemBinding.inflate(mLayoutInflater, parent, false)) + + override fun getItemCount() = mEntityList.size + + class ToolBoxBlockViewHolder(val binding: ToolboxBlockItemBinding) : + BaseRecyclerViewHolder(binding.root) { + + private var mIsExpand = false + private var mAdapter: ToolBoxItemAdapter? = null + + fun bindToolBoxBlock(toolBoxBlockEntity: ToolBoxBlockEntity) { + val context = binding.root.context + mAdapter = ToolBoxItemAdapter(context, true) + mAdapter?.setDataList(toolBoxBlockEntity.toolboxList.take(4)) + binding.titleTv.text = toolBoxBlockEntity.categoryName + binding.toolboxRv.layoutManager = GridLayoutManager(context, 2) + binding.toolboxRv.adapter = mAdapter + binding.expandContainer.goneIf(toolBoxBlockEntity.toolboxList.size < 5) + binding.expandContainer.setOnClickListener { + mIsExpand = !mIsExpand + if (mIsExpand) + mAdapter?.setDataList(toolBoxBlockEntity.toolboxList) + else + mAdapter?.setDataList(toolBoxBlockEntity.toolboxList.take(4)) + binding.expandTv.text = if (mIsExpand) "收起" else "展开全部" + binding.expandIv.run { + pivotX = width / 2F + pivotY = height / 2F + rotation = if (mIsExpand) 180F else 0F + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxItemAdapter.kt b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxItemAdapter.kt new file mode 100644 index 0000000000..0422e0e19e --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxItemAdapter.kt @@ -0,0 +1,174 @@ +package com.gh.gamecenter.toolbox + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.gh.base.BaseRecyclerViewHolder +import com.gh.common.constant.Config +import com.gh.common.constant.Constants +import com.gh.common.constant.ItemViewType +import com.gh.common.util.* +import com.gh.common.util.ImageUtils.display +import com.gh.gamecenter.NewsDetailActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.WebActivity.Companion.getWebByCollectionTools +import com.gh.gamecenter.adapter.viewholder.FooterViewHolder +import com.gh.gamecenter.baselist.ListExecutor +import com.gh.gamecenter.baselist.LoadStatus +import com.gh.gamecenter.databinding.ItemToolboxBinding +import com.gh.gamecenter.entity.ToolBoxEntity +import com.google.gson.Gson +import com.lightgame.adapter.BaseRecyclerAdapter +import java.util.* + +class ToolBoxItemAdapter(context: Context, val isBlockInside: Boolean = false, val viewModel: ToolBoxViewModel? = null) : + BaseRecyclerAdapter(context) { + + private var mEntityList: List = arrayListOf() + + fun setDataList(dataList: List) { + if (dataList.isEmpty() || mEntityList.size > dataList.size || viewModel?.mReset == true) { + mEntityList = dataList + notifyDataSetChanged() + return + } + ListExecutor.workerExecutor.execute { + val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int { + return mEntityList.size + } + + override fun getNewListSize(): Int { + return dataList.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = mEntityList.safelyGetInRelease(oldItemPosition) + val newItem = dataList.safelyGetInRelease(newItemPosition) + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItemPosition: Int, + newItemPosition: Int + ): Boolean { + val oldItem = mEntityList.safelyGetInRelease(oldItemPosition) + val newItem = dataList.safelyGetInRelease(newItemPosition) + return oldItem == newItem + } + }) + ListExecutor.uiExecutor.execute { + mEntityList = ArrayList(dataList) + diffResult.dispatchUpdatesTo(this) + } + } + } + + override fun getItemViewType(position: Int): Int { + return if (!isBlockInside && position == itemCount - 1 && itemCount != 0) { + ItemViewType.ITEM_FOOTER + } else { + ItemViewType.ITEM_BODY + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + if (viewType == ItemViewType.ITEM_FOOTER) { + FooterViewHolder( + mLayoutInflater.inflate( + R.layout.refresh_footerview, + parent, + false + ) + ) + } else { + ToolBoxViewHolder( + ItemToolboxBinding.bind( + mLayoutInflater.inflate( + R.layout.item_toolbox, + parent, + false + ) + ) + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is ToolBoxViewHolder) { + val toolBoxEntity = mEntityList[position] + initToolBoxViewHolder(holder, toolBoxEntity) + } else if (holder is FooterViewHolder) { + initFooterViewHolder(holder) + } + } + + private fun initFooterViewHolder(viewHolder: FooterViewHolder) { + viewHolder.initItemPadding() + viewModel?.let { + when (viewModel.searchStatus.value) { + LoadStatus.LIST_FAILED -> { + viewHolder.loading.visibility = View.GONE + viewHolder.hint.setText(R.string.loading_failed_retry) + viewHolder.itemView.isClickable = true + viewHolder.itemView.setOnClickListener { + viewModel.getSearchResultList() + viewModel.searchStatus.postValue(LoadStatus.LIST_LOADING) + } + } + LoadStatus.INIT_OVER, + LoadStatus.LIST_OVER -> { + viewHolder.loading.visibility = View.GONE + viewHolder.hint.setText(R.string.load_over_hint) + viewHolder.itemView.isClickable = false + } + LoadStatus.LIST_LOADING -> { + viewHolder.loading.visibility = View.VISIBLE + viewHolder.hint.setText(R.string.loading) + viewHolder.itemView.isClickable = false + } + } + } + } + + override fun getItemCount() = if (isBlockInside) mEntityList.size else if (mEntityList.isEmpty()) 0 else mEntityList.size + 1 + + private fun initToolBoxViewHolder(viewHolder: ToolBoxViewHolder, toolBoxEntity: ToolBoxEntity) { + viewHolder.binding.toolboxItemDes.text = toolBoxEntity.des + viewHolder.binding.toolboxItemTitle.text = toolBoxEntity.name + display(viewHolder.binding.toolboxItemGameThumb, toolBoxEntity.icon) + viewHolder.binding.divider.goneIf(isBlockInside) + viewHolder.binding.root.setOnClickListener { + if (SPUtils.getString(Constants.TOOLBOX_HISTORY).isEmpty()) { + SPUtils.setString(Constants.TOOLBOX_HISTORY, arrayListOf(toolBoxEntity.apply { lastOpenTime = System.currentTimeMillis() }).toJson()) + } else { + val list = arrayListOf(*Gson().fromJson(SPUtils.getString(Constants.TOOLBOX_HISTORY), Array::class.java)).apply { + if (contains(toolBoxEntity)) { + remove(toolBoxEntity) + } + add(0, toolBoxEntity.apply { lastOpenTime = System.currentTimeMillis() }) + } + SPUtils.setString(Constants.TOOLBOX_HISTORY, list.take(4).toJson()) + } + + val url = toolBoxEntity.url + if (url != null && url.contains(Config.URL_ARTICLE)) { + val newsId = url.substring(url.lastIndexOf("/") + 1, url.length - 5) // 5: ".html" + val intent = NewsDetailActivity.getIntentById(mContext, newsId, "工具箱列表") + mContext.startActivity(intent) + } else { + mContext.startActivity( + getWebByCollectionTools( + mContext, + toolBoxEntity, + false + ) + ) + } + } + } + + class ToolBoxViewHolder(val binding: ItemToolboxBinding) : + BaseRecyclerViewHolder(binding.root) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxViewModel.kt b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxViewModel.kt new file mode 100644 index 0000000000..afd9623f7a --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/toolbox/ToolBoxViewModel.kt @@ -0,0 +1,116 @@ +package com.gh.gamecenter.toolbox + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import com.gh.common.constant.Constants +import com.gh.common.util.* +import com.gh.gamecenter.baselist.LoadStatus +import com.gh.gamecenter.entity.ToolBoxBlockEntity +import com.gh.gamecenter.entity.ToolBoxEntity +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.google.gson.Gson +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import retrofit2.HttpException + +class ToolBoxViewModel(application: Application) : AndroidViewModel(application) { + + private var mPage = 1 + var mSearchKey = "" //搜索关键字 + var mReset = false + + val toolBoxBlockList = MutableLiveData>() + val searchResultList = MutableLiveData>() + val loadStatus = MutableLiveData() + val searchStatus = MutableLiveData() + + private val resultList = mutableListOf() + + fun getToolBoxList() { + RetrofitManager.getInstance().newApi.categoryToolKits + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Response>() { + override fun onResponse(response: List?) { + super.onResponse(response) + response?.let { + if (SPUtils.getString(Constants.TOOLBOX_HISTORY).isNotEmpty()) { + toolBoxBlockList.postValue(getNewList(it)) + } else { + toolBoxBlockList.postValue(it) + } + } + loadStatus.postValue(if (response.isNullOrEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_LOADED) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + loadStatus.postValue(LoadStatus.INIT_FAILED) + } + }) + } + + // 添加最近使用列表 + fun getNewList(list: List): List { + val historyList = listOf(*Gson().fromJson(SPUtils.getString(Constants.TOOLBOX_HISTORY), Array::class.java)) + val validList = mutableListOf().apply { + // 最多展示30天以内的打开记录 + for (item in historyList) { + val offsetDay = TimeUtils.getBeforeDays(item.lastOpenTime / 1000) + if (offsetDay <= 30) { + add(item) + } + } + } + SPUtils.setString(Constants.TOOLBOX_HISTORY, validList.take(4).toJson()) + return if (validList.isNotEmpty()) arrayListOf(ToolBoxBlockEntity(categoryName = "最近使用", toolboxList = validList)).apply { + addAll(list) + } else list + } + + fun getSearchResultList(reset: Boolean = false) { + mReset = reset + if (reset) { + mPage = 1 + resultList.clear() + } + RetrofitManager.getInstance().api.getToolKitData(mPage, UrlFilterUtils.getFilterQuery("keyword", mSearchKey)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Response>() { + override fun onResponse(response: List?) { + super.onResponse(response) + response?.let { + if (mPage != 1 && (response.isEmpty() || response.size < PAGE_SIZE)) { + searchStatus.postValue(LoadStatus.LIST_OVER) + resultList.addAll(response) + } else if (mPage == 1 && response.isEmpty()) { + searchStatus.postValue(LoadStatus.INIT_EMPTY) + resultList.clear() + } else if (mPage == 1 && response.size < PAGE_SIZE) { + searchStatus.postValue(LoadStatus.INIT_OVER) + resultList.clear() + resultList.addAll(response) + } else { + mPage++ + searchStatus.postValue(if (mPage == 1) LoadStatus.INIT_LOADED else LoadStatus.LIST_LOADED) + if (mPage == 1) resultList.clear() + resultList.addAll(response) + } + searchResultList.postValue(resultList) + } + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + searchStatus.postValue(if (mPage == 1) LoadStatus.INIT_FAILED else LoadStatus.LIST_FAILED) + } + }) + } + + companion object { + const val PAGE_SIZE = 20 + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/ic_toolbox_cancel.webp b/app/src/main/res/drawable-xxxhdpi/ic_toolbox_cancel.webp new file mode 100644 index 0000000000..f4bc16296f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_toolbox_cancel.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_toolbox_down_arrow.webp b/app/src/main/res/drawable-xxxhdpi/ic_toolbox_down_arrow.webp new file mode 100644 index 0000000000..f6bd0ad26a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_toolbox_down_arrow.webp differ diff --git a/app/src/main/res/layout/activity_toolbox_block.xml b/app/src/main/res/layout/activity_toolbox_block.xml new file mode 100644 index 0000000000..da78de6705 --- /dev/null +++ b/app/src/main/res/layout/activity_toolbox_block.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_toolbox.xml b/app/src/main/res/layout/item_toolbox.xml new file mode 100644 index 0000000000..d8a9cfcaae --- /dev/null +++ b/app/src/main/res/layout/item_toolbox.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/toolbox_block_item.xml b/app/src/main/res/layout/toolbox_block_item.xml new file mode 100644 index 0000000000..09c750ce40 --- /dev/null +++ b/app/src/main/res/layout/toolbox_block_item.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file