diff --git a/app/src/main/java/com/gh/base/fragment/BaseFragment.java b/app/src/main/java/com/gh/base/fragment/BaseFragment.java index 400cf89551..88881e1234 100644 --- a/app/src/main/java/com/gh/base/fragment/BaseFragment.java +++ b/app/src/main/java/com/gh/base/fragment/BaseFragment.java @@ -19,6 +19,9 @@ import androidx.fragment.app.FragmentTransaction; import com.gh.base.OnListClickListener; import com.gh.base.OnRequestCallBackListener; import com.gh.common.constant.Constants; +import com.gh.common.syncpage.ISyncAdapterHandler; +import com.gh.common.syncpage.SyncDataEntity; +import com.gh.common.syncpage.SyncPageRepository; import com.gh.gamecenter.BuildConfig; import com.gh.gamecenter.eventbus.EBMiPush; import com.lightgame.OnTitleClickListener; @@ -32,10 +35,18 @@ import org.greenrobot.eventbus.ThreadMode; import java.lang.ref.WeakReference; import java.util.List; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.recyclerview.widget.RecyclerView; import butterknife.ButterKnife; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; +import kotlin.Pair; import static com.gh.common.util.EntranceUtils.KEY_ENTRANCE; @@ -142,6 +153,36 @@ public abstract class BaseFragment extends Fragment implements OnRequestCallB ButterKnife.bind(this, mCachedView); initView(mCachedView); + + if (addSyncPageObserver()) { + initSyncPageObserver(); + } + } + + private void initSyncPageObserver() { + SyncPageRepository.INSTANCE.getSyncPageLiveData().observe(this, syncDataEntities -> { + try { + if (syncDataEntities == null || syncDataEntities.isEmpty()) return; + RecyclerView.Adapter adapter = provideSyncAdapter(); + if (!(adapter instanceof ISyncAdapterHandler)) return; + for (int i = 0; i < adapter.getItemCount(); i++) { + Pair syncKey = ((ISyncAdapterHandler) adapter).getSyncData(i); + if (syncKey == null) return; + for (SyncDataEntity syncDataEntity : syncDataEntities) { + if (syncDataEntity.getSyncId().equals(syncKey.getFirst())) { + boolean isSuccess = SyncPageRepository.INSTANCE.handleSyncData(syncKey.getSecond(), syncDataEntity); + if (isSuccess) adapter.notifyItemChanged(i); + } + } + } + } catch (Exception e) { + if (BuildConfig.DEBUG) { + throw e; + } else { + e.printStackTrace(); + } + } + }); } // 必须的有subscribe才能register @@ -252,4 +293,12 @@ public abstract class BaseFragment extends Fragment implements OnRequestCallB return this; } + @Nullable + protected RecyclerView.Adapter provideSyncAdapter() { + return null; + } + + protected boolean addSyncPageObserver() { + return false; + } } diff --git a/app/src/main/java/com/gh/common/annotation/SyncPage.java b/app/src/main/java/com/gh/common/annotation/SyncPage.java new file mode 100644 index 0000000000..a2a670cb87 --- /dev/null +++ b/app/src/main/java/com/gh/common/annotation/SyncPage.java @@ -0,0 +1,12 @@ +package com.gh.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface SyncPage { + String[] syncNames(); +} diff --git a/app/src/main/java/com/gh/common/syncpage/ISyncAdapterHandler.kt b/app/src/main/java/com/gh/common/syncpage/ISyncAdapterHandler.kt new file mode 100644 index 0000000000..4fafd0ad62 --- /dev/null +++ b/app/src/main/java/com/gh/common/syncpage/ISyncAdapterHandler.kt @@ -0,0 +1,12 @@ +package com.gh.common.syncpage + +interface ISyncAdapterHandler { + + /** + * @param position position to query + * @return Pair first: item sync id + * Pair second: item data entity + */ + fun getSyncData(position: Int): Pair? + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/syncpage/SyncDataEntity.kt b/app/src/main/java/com/gh/common/syncpage/SyncDataEntity.kt new file mode 100644 index 0000000000..611445448f --- /dev/null +++ b/app/src/main/java/com/gh/common/syncpage/SyncDataEntity.kt @@ -0,0 +1,43 @@ +package com.gh.common.syncpage + +import androidx.annotation.Keep + +@Keep +data class SyncDataEntity( + /** + * 标识一条数据的唯一ID + * + * 与[ISyncAdapterHandler.getSyncData]返回的Pair first一致 + */ + val syncId: String, + + /** + * 需要同步的字段名 + * + * 与@SyncPage注解的值一致 + */ + val syncFieldName: String?, + + /** + * 需要同步的具体内容 + */ + val syncFieldValue: Any?, + + /** + * 同步完一次是否自动删除 + */ + val remove: Boolean = true, + + /** + * 是否需要查询同步实体的父级字段 + * + * 由于反射可能会导致较大的性能消耗,默认关闭,具体按实际情况开启 + */ + val checkInherited: Boolean = false, + + /** + * 是否需要查询同步实体的嵌套实体内容 + * + * 由于反射可能会导致较大的性能消耗,默认关闭,具体按实际情况开启 + */ + val checkFieldEntity: Boolean = false) \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/syncpage/SyncFieldConstants.kt b/app/src/main/java/com/gh/common/syncpage/SyncFieldConstants.kt new file mode 100644 index 0000000000..dad7d82695 --- /dev/null +++ b/app/src/main/java/com/gh/common/syncpage/SyncFieldConstants.kt @@ -0,0 +1,17 @@ +package com.gh.common.syncpage + +object SyncFieldConstants { + + // 是否点赞 + const val ANSWER_VOTE = "ANSWER_VOTE" + const val ARTICLE_VOTE = "ARTICLE_VOTE" + + // 赞同数量 + const val ANSWER_VOTE_COUNT = "ANSWER_VOTE_COUNT" + const val ARTICLE_VOTE_COUNT = "ARTICLE_VOTE_COUNT" + + // 评论数量 + const val ANSWER_COMMENT_COUNT = "ANSWER_COMMENT_COUNT" + const val ARTICLE_COMMENT_COUNT = "ARTICLE_COMMENT_COUNT" + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/syncpage/SyncPageRepository.kt b/app/src/main/java/com/gh/common/syncpage/SyncPageRepository.kt new file mode 100644 index 0000000000..5c71f2e6a1 --- /dev/null +++ b/app/src/main/java/com/gh/common/syncpage/SyncPageRepository.kt @@ -0,0 +1,116 @@ +package com.gh.common.syncpage + +import androidx.lifecycle.MutableLiveData +import com.gh.common.annotation.SyncPage +import com.gh.common.util.toJson +import com.gh.common.util.tryCatchInRelease +import com.halo.assistant.HaloApp +import com.lightgame.utils.Utils +import java.lang.reflect.Field +import java.util.concurrent.CopyOnWriteArrayList + +/** + * 页面之间实现数据同步(主要是单个字段的同步) + * + * 实现思路: + * 1.把需要同步的数据以一个特殊ID作为唯一标识,并存于一个全局的仓库 + * 2.利用LiveData进行页面回调,再通过[ISyncAdapterHandler.getSyncData]找到需要被同步的数据 + * 3.最后利用反射进行数据替换,具体请见[SyncPageRepository.replaceSyncData] + * + * 具体的接入方式(以列表为例): + * 1.通过[SyncPageRepository.postSyncData]提交同步数据 + * 2.在Fragment重写[BaseFragment.addSyncPageObserver]开启同步事件监听 + * 3.在Fragment重写[BaseFragment.provideSyncAdapter]提供获取数据的来源 + * - [BaseFragment.provideSyncAdapter]提供的Adapter必须实现[ISyncAdapterHandler]接口 + */ +object SyncPageRepository { + + // 只有在新增操作时,才需要进行页面刷新 + val syncPageLiveData = MutableLiveData>() + + val syncDataList = CopyOnWriteArrayList() + + @JvmStatic + fun clearSyncData() { + tryCatchInRelease { + syncDataList.clear() + } + } + + // 提交同步数据 + fun postSyncData(entity: SyncDataEntity) { + tryCatchInRelease { + // 检查是否存在重复操作 + for (syncDataEntity in syncDataList) { + if (syncDataEntity.syncId == entity.syncId && syncDataEntity.syncFieldName == entity.syncFieldName) { + syncDataList.remove(syncDataEntity) + syncDataList.add(entity) + return + } + } + + syncDataList.add(entity) + syncPageLiveData.postValue(syncDataList) + + Utils.log("SyncPageRepository postSyncData->" + entity.toJson()) + } + } + + fun handleSyncData(rawData: Any, syncData: SyncDataEntity): Boolean { + val fields = if (syncData.checkInherited) { + getAllFieldsList(rawData::class.java) + } else rawData::class.java.declaredFields.toList() + + var isNeedNotify = false + for (field in fields) { + isNeedNotify = replaceSyncData(field, rawData, syncData) + if (isNeedNotify) break + } + + if (syncData.remove) syncDataList.remove(syncData) + + if (!isNeedNotify) { + Utils.log("SyncPageRepository sync failure-> " + syncData.syncFieldName) + } else { + Utils.log("SyncPageRepository sync success-> " + syncData.syncFieldName) + } + + return isNeedNotify + } + + private fun replaceSyncData(field: Field, extractObject: Any, syncData: SyncDataEntity): Boolean { + field.getAnnotation(SyncPage::class.java)?.syncNames?.forEach { syncName -> + if (syncName == syncData.syncFieldName) { + field.isAccessible = true + field.set(extractObject, syncData.syncFieldValue) + return true + } + } + + if (syncData.checkFieldEntity) { + // 递归查询 + val pkgName = field.type.getPackage()?.name ?: return false + if (pkgName.contains(HaloApp.getInstance().application.packageName)) { + field.isAccessible = true + val extractEntityObject = field.get(extractObject) ?: return false + field.type.declaredFields.forEach { + if (replaceSyncData(it, extractEntityObject, syncData)) return true + } + } + } + return false + } + + // 包括父类 Field 对象 + private fun getAllFieldsList(cls: Class<*>?): List { + if (cls == null) return arrayListOf() + val allFields = arrayListOf() + var currentClass = cls + while (currentClass != null) { + val declaredFields = currentClass.declaredFields + allFields.addAll(declaredFields) + currentClass = currentClass.superclass + } + return allFields + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/syncpage/example/ExampleAdapter.kt b/app/src/main/java/com/gh/common/syncpage/example/ExampleAdapter.kt new file mode 100644 index 0000000000..21aa34a0ea --- /dev/null +++ b/app/src/main/java/com/gh/common/syncpage/example/ExampleAdapter.kt @@ -0,0 +1,98 @@ +package com.gh.common.syncpage.example + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.ItemViewType +import com.gh.common.syncpage.ISyncAdapterHandler +import com.gh.gamecenter.R +import com.gh.gamecenter.adapter.viewholder.FooterViewHolder +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.databinding.CommunityAnswerItemBinding +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.qa.answer.CommunityAnswerItemViewHolder +import com.gh.gamecenter.qa.answer.detail.AnswerDetailActivity +import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity +import com.gh.gamecenter.qa.entity.AnswerEntity +import com.gh.gamecenter.qa.entity.Questions +import com.gh.gamecenter.qa.questions.detail.QuestionsDetailActivity + +class ExampleAdapter(context: Context) : ListAdapter(context), ISyncAdapterHandler { + + override fun areItemsTheSame(oldItem: AnswerEntity?, newItem: AnswerEntity?): Boolean { + return oldItem?.id == newItem?.id + } + + override fun getItemViewType(position: Int): Int { + if (position == itemCount - 1) return ItemViewType.ITEM_FOOTER + return ItemViewType.ITEM_BODY + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view: View + return when (viewType) { + ItemViewType.ITEM_FOOTER -> { + view = mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false) + FooterViewHolder(view) + } + else -> { + view = mLayoutInflater.inflate(R.layout.community_answer_item, parent, false) + CommunityAnswerItemViewHolder(CommunityAnswerItemBinding.bind(view)) + } + } + } + + override fun getItemCount(): Int { + return if (mEntityList.isNotEmpty()) mEntityList.size + FOOTER_ITEM_COUNT else 0 + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (getItemViewType(position)) { + ItemViewType.ITEM_BODY -> { + val answer = mEntityList[position] + if ("community_article" == answer.type) { + val questions = Questions() + questions.title = answer.articleTitle + answer.questions = questions + } + + val answerViewHolder = holder as CommunityAnswerItemViewHolder + val binding = answerViewHolder.binding + answerViewHolder.bindAnswerItem(answer, "", getPath()) + binding.title.setOnClickListener { + if ("community_article" == answer.type) { + mContext.startActivity(ArticleDetailActivity.getIntent(mContext, UserManager.getInstance().community, answer.id!!, "", getPath())) + } else { + val questions = answer.questions + mContext.startActivity(QuestionsDetailActivity.getIntent(mContext, questions.id, "", getPath())) + } + } + + answerViewHolder.itemView.setOnClickListener { + if ("community_article" == answer.type) { + mContext.startActivity(ArticleDetailActivity.getIntent(mContext, UserManager.getInstance().community, answer.id!!, "", getPath())) + } else { + mContext.startActivity(AnswerDetailActivity.getIntent(mContext, answer.id, "", getPath())) + } + } + } + ItemViewType.ITEM_FOOTER -> { + val footerViewHolder = holder as FooterViewHolder + footerViewHolder.initItemPadding() + footerViewHolder.initFooterViewHolder(mIsLoading, mIsNetworkError, mIsOver, R.string.ask_loadover_hint) + } + } + } + + override fun getSyncData(position: Int): Pair? { + if (position >= mEntityList.size) return null + val entity = mEntityList[position] + return Pair(entity.id ?: "", entity) + } + + fun getPath(): String { + return "问答-推荐-按时间" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/syncpage/example/ExampleFragment.kt b/app/src/main/java/com/gh/common/syncpage/example/ExampleFragment.kt new file mode 100644 index 0000000000..8993998e3e --- /dev/null +++ b/app/src/main/java/com/gh/common/syncpage/example/ExampleFragment.kt @@ -0,0 +1,65 @@ +package com.gh.common.syncpage.example + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.view.VerticalItemDecoration +import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.baselist.NormalListViewModel +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.qa.entity.AnswerEntity +import com.gh.gamecenter.retrofit.RetrofitManager +import com.halo.assistant.HaloApp +import io.reactivex.Observable + +class ExampleFragment : ListFragment>() { + + private var mAdapter: ExampleAdapter? = null + + override fun provideListAdapter(): ExampleAdapter { + if (mAdapter == null) { + mAdapter = ExampleAdapter(requireContext()) + } + return mAdapter!! + } + + override fun getItemDecoration(): RecyclerView.ItemDecoration { + return VerticalItemDecoration(context, 8F, false) + } + + override fun provideDataObservable(page: Int): Observable> { + return RetrofitManager.getInstance(context).api.getCommunitiesRecommendNewest(UserManager.getInstance().community.id, page) + } + + override fun provideListViewModel(): NormalListViewModel { + val factory = NormalListViewModel.Factory(HaloApp.getInstance().application, this) + return ViewModelProviders.of(this, factory).get(NormalListViewModel::class.java) as NormalListViewModel + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) +// SyncPageRepository.syncPageLiveData.observe(this, Observer { +// it ?: return@Observer +// val adapter = mListRv.adapter +// if (adapter !is ISyncAdapterHandler) return@Observer +// for(position in 0 until adapter.itemCount) { +// val syncKey = adapter.getSyncData(position) +// for (syncDataEntity in it) { +// if (syncDataEntity.syncId == syncKey?.first) { +// val isSuccess = SyncPageRepository.handleSyncData(syncKey.second, syncDataEntity) +// if (isSuccess) adapter.notifyItemChanged(position) +// } +// } +// } +// }) + } + + override fun provideSyncAdapter(): RecyclerView.Adapter<*>? { + return mListRv.adapter + } + + override fun addSyncPageObserver(): Boolean { + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt index 2d89277f7c..4891a4a489 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt @@ -1,6 +1,8 @@ package com.gh.gamecenter.entity import android.os.Parcelable +import com.gh.common.annotation.SyncPage +import com.gh.common.syncpage.SyncFieldConstants import com.google.gson.annotations.SerializedName import kotlinx.android.parcel.Parcelize @@ -14,6 +16,7 @@ class MeEntity(@SerializedName("is_community_voted") @SerializedName("is_user_invite") var isUserInvite: Boolean = false, + @SyncPage(syncNames = [SyncFieldConstants.ANSWER_VOTE]) @SerializedName("is_answer_voted") var isAnswerVoted: Boolean = false, @@ -50,10 +53,10 @@ class MeEntity(@SerializedName("is_community_voted") @SerializedName("is_toolkit_favorite") var isToolkitFavorite: Boolean = false, - @SerializedName("is_comment_own", alternate = ["is_answer_commented", "is_community_article_commented", "is_video_commented"]) + @SerializedName("is_comment_own", alternate = ["is_answer_commented", "is_community_article_commented", "is_video_commented"]) var isCommentOwner: Boolean = false, // 是否是当前评论的拥有者 - @SerializedName("is_comment_voted", alternate = ["is_answer_comment_voted","is_video_comment_voted", "is_community_article_comment_voted"]) + @SerializedName("is_comment_voted", alternate = ["is_answer_comment_voted", "is_video_comment_voted", "is_community_article_comment_voted"]) var isCommentVoted: Boolean = false, // 是否已经点赞过当前评论 @SerializedName("is_version_requested") @@ -65,6 +68,7 @@ class MeEntity(@SerializedName("is_community_voted") @SerializedName("is_favorite") var isCommunityArticleFavorite: Boolean = false, + @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_VOTE]) @SerializedName("is_vote") var isCommunityArticleVote: Boolean = false, diff --git a/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java b/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java index fdd15150b7..4f0a69985b 100644 --- a/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java +++ b/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java @@ -26,6 +26,7 @@ import com.facebook.imagepipeline.image.ImageInfo; import com.gh.base.OnDoubleTapListener; import com.gh.base.fragment.BaseFragment_ViewPager_Checkable; import com.gh.common.constant.Config; +import com.gh.common.syncpage.SyncPageRepository; import com.gh.common.util.DataUtils; import com.gh.common.util.DisplayUtils; import com.gh.common.util.EntranceUtils; @@ -264,6 +265,10 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable implem super.onResume(); //mViewModel.getDiscoveryData(false); MessageUnreadRepository.INSTANCE.loadMessageUnreadTotal(); + + if (isEverPause) { + mBaseHandler.postDelayed(SyncPageRepository::clearSyncData, 2000); + } } @Override diff --git a/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailViewModel.kt b/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailViewModel.kt index 6ab183c6b3..ae7f9ac23c 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailViewModel.kt @@ -6,6 +6,9 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.gh.common.history.HistoryHelper +import com.gh.common.syncpage.SyncDataEntity +import com.gh.common.syncpage.SyncFieldConstants +import com.gh.common.syncpage.SyncPageRepository import com.gh.common.util.* import com.gh.gamecenter.R import com.gh.gamecenter.entity.SpecialColumn @@ -88,6 +91,8 @@ class AnswerDetailViewModel(application: Application) : AndroidViewModel(applica val apiResponse = ApiResponse() apiResponse.data = response mVoteLiveData.postValue(apiResponse) + + syncVoteData(answerId) } override fun onFailure(e: HttpException?) { @@ -111,6 +116,8 @@ class AnswerDetailViewModel(application: Application) : AndroidViewModel(applica val apiResponse = ApiResponse() apiResponse.data = response mVoteLiveData.postValue(apiResponse) + + syncVoteData(answerId) } override fun onFailure(e: HttpException?) { @@ -134,6 +141,8 @@ class AnswerDetailViewModel(application: Application) : AndroidViewModel(applica answerDetail?.me?.isAnswerVoted = false dislike.postValue(true) + + syncVoteData(answerId) } override fun onFailure(e: HttpException?) { @@ -150,6 +159,8 @@ class AnswerDetailViewModel(application: Application) : AndroidViewModel(applica override fun onResponse(response: ResponseBody?) { answerDetail?.me?.isAnswerOpposed = false dislike.postValue(false) + + syncVoteData(answerId) } override fun onFailure(e: HttpException?) { @@ -158,6 +169,16 @@ class AnswerDetailViewModel(application: Application) : AndroidViewModel(applica }) } + private fun syncVoteData(answerId: String) { + SyncPageRepository.postSyncData(SyncDataEntity(answerId, + SyncFieldConstants.ANSWER_VOTE_COUNT, + answerDetail!!.vote)) + SyncPageRepository.postSyncData(SyncDataEntity(answerId, + SyncFieldConstants.ANSWER_VOTE, + answerDetail?.me?.isAnswerVoted, + checkFieldEntity = true)) + } + fun collectAnswer(answerId: String) { CollectionUtils.postCollection(getApplication(), answerId, CollectionUtils.CollectionType.answer, object : CollectionUtils.OnCollectionListener { override fun onSuccess() { diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListAdapter.kt b/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListAdapter.kt index 79d95de571..a040c80018 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListAdapter.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.gh.common.constant.ItemViewType +import com.gh.common.syncpage.ISyncAdapterHandler import com.gh.gamecenter.R import com.gh.gamecenter.adapter.viewholder.FooterViewHolder import com.gh.gamecenter.baselist.ListAdapter @@ -15,7 +16,7 @@ import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity import com.gh.gamecenter.qa.entity.ArticleEntity import com.gh.gamecenter.qa.entity.Questions -class SimpleArticleListAdapter(context: Context) : ListAdapter(context) { +class SimpleArticleListAdapter(context: Context) : ListAdapter(context), ISyncAdapterHandler { override fun areItemsTheSame(oldItem: ArticleEntity?, newItem: ArticleEntity?): Boolean { return oldItem?.id == newItem?.id @@ -72,4 +73,10 @@ class SimpleArticleListAdapter(context: Context) : ListAdapter(co } } + override fun getSyncData(position: Int): Pair? { + if (position >= mEntityList.size) return null + val entity = mEntityList[position] + return Pair(entity.id, entity) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListFragment.kt index a7df860721..19aefcce43 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/SimpleArticleListFragment.kt @@ -86,4 +86,12 @@ class SimpleArticleListFragment : ListFragment? { + return mAdapter + } + + override fun addSyncPageObserver(): Boolean { + return true + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailViewModel.kt b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailViewModel.kt index 60275409be..cb8dc7a344 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailViewModel.kt @@ -4,6 +4,9 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.gh.common.syncpage.SyncDataEntity +import com.gh.common.syncpage.SyncFieldConstants +import com.gh.common.syncpage.SyncPageRepository import com.gh.common.util.CollectionUtils import com.gh.common.util.ErrorHelper import com.gh.gamecenter.R @@ -79,6 +82,8 @@ class ArticleDetailViewModel(application: Application) : AndroidViewModel(applic detailEntity!!.count.vote-- like.postValue(response) + + syncVoteData() } override fun onFailure(e: HttpException?) { @@ -99,6 +104,8 @@ class ArticleDetailViewModel(application: Application) : AndroidViewModel(applic detailEntity!!.count.vote++ like.postValue(response) + + syncVoteData() } override fun onFailure(e: HttpException?) { @@ -119,6 +126,8 @@ class ArticleDetailViewModel(application: Application) : AndroidViewModel(applic detailEntity?.me?.isCommunityArticleVote = false dislike.postValue(true) + + syncVoteData() } override fun onFailure(e: HttpException?) { @@ -135,6 +144,8 @@ class ArticleDetailViewModel(application: Application) : AndroidViewModel(applic detailEntity?.me?.isCommunityArticleOppose = false dislike.postValue(false) + + syncVoteData() } override fun onFailure(e: HttpException?) { @@ -143,6 +154,19 @@ class ArticleDetailViewModel(application: Application) : AndroidViewModel(applic }) } + private fun syncVoteData() { + articleId?.apply { + SyncPageRepository.postSyncData(SyncDataEntity(this, + SyncFieldConstants.ARTICLE_VOTE_COUNT, + detailEntity?.count?.vote, + checkFieldEntity = true)) + SyncPageRepository.postSyncData(SyncDataEntity(this, + SyncFieldConstants.ARTICLE_VOTE, + detailEntity?.me?.isCommunityArticleVote, + checkFieldEntity = true)) + } + } + fun collectionCommand(isCollection: Boolean, callback: (isFollow: Boolean) -> Unit) { val observable = if (isCollection) { mApi.postCommunityArticleFavorites(UserManager.getInstance().userId, community?.id, articleId) diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt index 9ce633f4b1..d6c339335a 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt @@ -21,6 +21,9 @@ import butterknife.BindView import butterknife.OnClick import butterknife.Optional import com.gh.base.fragment.BaseDialogWrapperFragment +import com.gh.common.syncpage.SyncDataEntity +import com.gh.common.syncpage.SyncFieldConstants +import com.gh.common.syncpage.SyncPageRepository import com.gh.common.util.* import com.gh.common.view.VerticalItemDecoration import com.gh.gamecenter.R @@ -93,6 +96,17 @@ open class NewCommentFragment : ListFragment mCommentListener?.onCommentDraftChange("") } commentEt.postDelayed({ setSoftInput(false) }, 100) + + if (mCommentType == CommentType.COMMUNITY_ARTICLE) { + SyncPageRepository.postSyncData(SyncDataEntity(mArticleId, + SyncFieldConstants.ARTICLE_COMMENT_COUNT, + mCommentCount, + checkFieldEntity = true)) + } else if (mCommentType == CommentType.ANSWER) { + SyncPageRepository.postSyncData(SyncDataEntity(mAnswerId, + SyncFieldConstants.ANSWER_COMMENT_COUNT, + mCommentCount)) + } } apiResponse.httpException != null -> { mSendingDialog?.dismiss() diff --git a/app/src/main/java/com/gh/gamecenter/qa/entity/AnswerEntity.kt b/app/src/main/java/com/gh/gamecenter/qa/entity/AnswerEntity.kt index 2481ca7d78..60b1acf0fe 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/entity/AnswerEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/entity/AnswerEntity.kt @@ -7,6 +7,8 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey import androidx.room.TypeConverters +import com.gh.common.annotation.SyncPage +import com.gh.common.syncpage.SyncFieldConstants import com.gh.gamecenter.entity.MeEntity import com.gh.gamecenter.entity.UserEntity import com.gh.gamecenter.room.converter.AnswerUserConverter @@ -47,6 +49,7 @@ class AnswerEntity() : Parcelable { @TypeConverters(CommunityVideoConverter::class) var videos: List = ArrayList() + @SyncPage(syncNames = [SyncFieldConstants.ANSWER_VOTE_COUNT, SyncFieldConstants.ARTICLE_VOTE_COUNT]) var vote: Int = 0 @TypeConverters(AnswerUserConverter::class) @@ -59,6 +62,7 @@ class AnswerEntity() : Parcelable { @SerializedName("community_name") var communityName: String? = null + @SyncPage(syncNames = [SyncFieldConstants.ANSWER_COMMENT_COUNT, SyncFieldConstants.ARTICLE_COMMENT_COUNT]) @SerializedName("comment_count") var commentCount: Int = 0 diff --git a/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt b/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt index 4e74f454fa..23e34bd8e5 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt @@ -5,6 +5,8 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey import androidx.room.TypeConverters +import com.gh.common.annotation.SyncPage +import com.gh.common.syncpage.SyncFieldConstants import com.gh.gamecenter.entity.CommunityEntity import com.gh.gamecenter.entity.MeEntity import com.gh.gamecenter.entity.UserEntity @@ -69,7 +71,9 @@ data class ArticleEntity( @Parcelize data class Count( + @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_COMMENT_COUNT]) var comment: Int = 0, + @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_VOTE_COUNT]) var vote: Int = 0, var answer: Int = 0) : Parcelable