diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a93d3af35e..47b6c37bc0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -615,6 +615,10 @@ android:name=".personal.NewPersonalActivity" android:screenOrientation="portrait" /> + + diff --git a/app/src/main/java/com/gh/common/simulator/SimulatorDownloadManager.kt b/app/src/main/java/com/gh/common/simulator/SimulatorDownloadManager.kt index 5daca02c94..fc42fb695c 100644 --- a/app/src/main/java/com/gh/common/simulator/SimulatorDownloadManager.kt +++ b/app/src/main/java/com/gh/common/simulator/SimulatorDownloadManager.kt @@ -118,7 +118,7 @@ class SimulatorDownloadManager private constructor() { key = if (shouldShowUpdate && isInstalled) "更新弹窗" else "下载弹窗", logShowEvent = true ) - DialogUtils.showNewAlertDialog(context, title, message, negativeText, positiveText, trackableEntity, Gravity.LEFT, { + DialogUtils.showNewAlertDialog(context, title, message, negativeText, positiveText, trackableEntity, Gravity.LEFT, false, { if (shouldShowUpdate && isInstalled) { cancelCallback?.invoke() MtaHelper.onEvent(trackableEntity.event, trackableEntity.key, "点击下次再说") diff --git a/app/src/main/java/com/gh/common/util/DialogUtils.java b/app/src/main/java/com/gh/common/util/DialogUtils.java index 43fcfcb809..dc0d50621a 100644 --- a/app/src/main/java/com/gh/common/util/DialogUtils.java +++ b/app/src/main/java/com/gh/common/util/DialogUtils.java @@ -350,7 +350,7 @@ public class DialogUtils { * @param cmListener 确认按钮监听 */ public static Dialog showNewAlertDialog(Context context, String title, CharSequence message - , String negative, String positive, TrackableEntity trackableEntity, int gravity, final CancelListener clListener, final ConfirmListener cmListener) { + , String negative, String positive, TrackableEntity trackableEntity, int gravity, boolean shouldShowCloseBtn, final CancelListener clListener, final ConfirmListener cmListener) { context = checkDialogContext(context); final Dialog dialog; if (trackableEntity != null) { @@ -373,6 +373,7 @@ public class DialogUtils { TextView cancelBtn = contentView.findViewById(R.id.cancel); TextView confirmBtn = contentView.findViewById(R.id.confirm); View middleLine = contentView.findViewById(R.id.middle_line); + View closeIv = contentView.findViewById(R.id.closeIv); titleTv.setGravity(gravity); contentTv.setGravity(gravity); @@ -389,6 +390,8 @@ public class DialogUtils { confirmBtn.setVisibility(View.GONE); middleLine.setVisibility(View.GONE); } + closeIv.setVisibility(shouldShowCloseBtn ? View.VISIBLE : View.GONE); + closeIv.setOnClickListener(v -> dialog.dismiss()); cancelBtn.setOnClickListener(v -> { if (clListener != null) clListener.onCancel(); @@ -413,7 +416,12 @@ public class DialogUtils { public static Dialog showNewAlertDialog(Context context, String title, CharSequence message , String negative, String positive, final CancelListener clListener, final ConfirmListener cmListener) { - return showNewAlertDialog(context, title, message, negative, positive, null, Gravity.LEFT, clListener, cmListener); + return showNewAlertDialog(context, title, message, negative, positive, null, Gravity.LEFT, false, clListener, cmListener); + } + + public static Dialog showNewAlertDialog(Context context, String title, CharSequence message + , String negative, String positive, int gravity, boolean shouldShowCloseBtn, final CancelListener clListener, final ConfirmListener cmListener) { + return showNewAlertDialog(context, title, message, negative, positive, null, gravity, shouldShowCloseBtn, clListener, cmListener); } /** @@ -1951,8 +1959,8 @@ public class DialogUtils { final Dialog dialog = new Dialog(context, R.style.DialogWindowTransparent); View contentView = LayoutInflater.from(context).inflate(R.layout.dialog_energy, null); - ((TextView)contentView.findViewById(R.id.userName)).setText("\"" + userName + "\""); - ((TextView)contentView.findViewById(R.id.energy)).setText(energy + ""); + ((TextView) contentView.findViewById(R.id.userName)).setText("\"" + userName + "\""); + ((TextView) contentView.findViewById(R.id.energy)).setText(energy + ""); contentView.findViewById(R.id.dialog_positive).setOnClickListener(v -> { dialog.dismiss(); diff --git a/app/src/main/java/com/gh/common/view/RichEditor.java b/app/src/main/java/com/gh/common/view/RichEditor.java index b9fbe2bcc4..c48ec0265d 100644 --- a/app/src/main/java/com/gh/common/view/RichEditor.java +++ b/app/src/main/java/com/gh/common/view/RichEditor.java @@ -24,6 +24,7 @@ import com.gh.common.util.HtmlUtils; import com.gh.common.util.ImageUtils; import com.gh.common.util.MtaHelper; import com.gh.common.util.NetworkUtils; +import com.gh.common.util.PackageUtils; import com.gh.common.util.RichEditorUtils; import com.gh.common.util.SPUtils; import com.gh.gamecenter.BuildConfig; @@ -662,6 +663,16 @@ public class RichEditor extends WebView { return DeviceUtils.getNetwork(HaloApp.getInstance().getApplication()); } + @JavascriptInterface + public String getAppVersion() { + return BuildConfig.VERSION_NAME; + } + + @JavascriptInterface + public int getAppVersionCode() { + return PackageUtils.getVersionCode(); + } + /** * 显示toast * diff --git a/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowAdapter.kt b/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowAdapter.kt index f5f9a4ea8b..6949f5edc5 100644 --- a/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/forum/follow/ForumMyFollowAdapter.kt @@ -69,7 +69,7 @@ class ForumMyFollowAdapter(context: Context, val mViewModel: ForumMyFollowViewMo popupWindow.dismiss() when (text) { "取消关注" -> { - DialogUtils.showNewAlertDialog(mContext, "提示", "确定取消关注", "暂不", "确定", null, Gravity.CENTER, {}, { + DialogUtils.showNewAlertDialog(mContext, "提示", "确定取消关注", "暂不", "确定", null, Gravity.CENTER, false, {}, { MtaHelper.onEvent("论坛首页", "我关注的论坛", "取消关注") mViewModel.unFollowForum(entity.id) { EventBus.getDefault().post(EBForumFollowChange(entity, false)) diff --git a/app/src/main/java/com/gh/gamecenter/forum/select/ForumAdapter.kt b/app/src/main/java/com/gh/gamecenter/forum/select/ForumAdapter.kt index 8dc6b38389..84e44ab883 100644 --- a/app/src/main/java/com/gh/gamecenter/forum/select/ForumAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/forum/select/ForumAdapter.kt @@ -54,7 +54,7 @@ class ForumAdapter(val context: Context, val mViewModel: ForumSelectViewModel?, mContext.ifLogin("论坛-选择论坛") { debounceActionWithInterval(it.id) { if (forumEntity.isFollow) { - DialogUtils.showNewAlertDialog(mContext, "提示", "确定取消关注", "暂不", "确定", null, Gravity.CENTER, {}, { + DialogUtils.showNewAlertDialog(mContext, "提示", "确定取消关注", "暂不", "确定", null, Gravity.CENTER, false, {}, { mViewModel?.unFollowForum(forumEntity.id) { MtaHelper.onEvent("论坛首页", "选择论坛", "关注") forumEntity.isFollow = false diff --git a/app/src/main/java/com/gh/gamecenter/qa/answer/edit/AnswerEditActivity.kt b/app/src/main/java/com/gh/gamecenter/qa/answer/edit/AnswerEditActivity.kt index 55a83b1203..0bd6ab2efa 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/answer/edit/AnswerEditActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/answer/edit/AnswerEditActivity.kt @@ -31,8 +31,6 @@ import com.gh.gamecenter.qa.entity.Questions import com.gh.gamecenter.video.VideoVerifyItemViewHolder import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel import com.halo.assistant.HaloApp -import com.lightgame.utils.Util_System_Keyboard -import com.lightgame.utils.Utils import com.zhihu.matisse.Matisse import com.zhihu.matisse.MimeType import org.json.JSONArray @@ -44,6 +42,7 @@ import org.json.JSONObject class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { private lateinit var mMenuDraft: MenuItem + private lateinit var mMenuPost: MenuItem private lateinit var mBinding: FragmentAnswerEditBinding private lateinit var mViewModel: AnswerEditViewModel @@ -84,6 +83,7 @@ class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { super.onCreate(savedInstanceState) setToolbarMenu(R.menu.menu_answer_post) mMenuDraft = mToolbar.menu.findItem(R.id.menu_draft) + mMenuPost = mToolbar.menu.findItem(R.id.menu_answer_post) mCommunityName = intent?.getStringExtra(EntranceUtils.KEY_COMMUNITY_NAME) mOpenAnswerInNewPage = intent?.getBooleanExtra(EntranceUtils.KEY_ANSWER_OPEN_IN_NEW_PAGE, false)!! mBinding = FragmentAnswerEditBinding.bind(mContentView) @@ -108,6 +108,7 @@ class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { mBinding.answerPlaceholder.visibility = if (t.contains(" @@ -134,6 +135,7 @@ class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { mKeyboardHeightProvider = KeyboardHeightProvider(this) mBinding.root.post { mKeyboardHeightProvider?.start() } + checkPostButtonEnable() } override fun getLayoutId(): Int { @@ -277,6 +279,7 @@ class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { mViewModel.draftsLiveData.observe(this, Observer { mRichEditor.setHtml(it, false) requestFocusAndMoveCursorToEnd() + checkPostButtonEnable() }) mViewModel.saveDraftsLiveData.observeNonNull(this) { @@ -375,23 +378,32 @@ class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { val answerContent = getReplaceRealContent() mRichEditor.showLinkStyle() if (mRichEditor.hasPlaceholderImage()) { - ToastUtils.showToast("图片正在上传中") + ToastUtils.showToast("图片上传ing") return@postDelayed } - // filter rule - val answerLength = HtmlUtils.stripHtml(answerContent).length - if (answerLength < MIN_ANSWER_TEXT_LENGTH) { - ToastUtils.showToast(getString(R.string.answer_beneath_length_limit), if (mIsKeyBoardShow) Gravity.CENTER else -1) - return@postDelayed - } else if (answerLength > MAX_ANSWER_TEXT_LENGTH) { - ToastUtils.showToast("回答最多输入10000个字", if (mIsKeyBoardShow) Gravity.CENTER else -1) - return@postDelayed + if (checkData(answerContent)) { + mViewModel.postAnswer(answerContent) } - mViewModel.postAnswer(answerContent) }, 100) } } + private fun checkData(answerContent: String, isShowToast: Boolean = true): Boolean { + val answerLength = HtmlUtils.stripHtml(answerContent).length + if (answerLength < MIN_ANSWER_TEXT_LENGTH) { + if (isShowToast) ToastUtils.showToast(getString(R.string.answer_beneath_length_limit), if (mIsKeyBoardShow) Gravity.CENTER else -1) + return false + } else if (answerLength > MAX_ANSWER_TEXT_LENGTH) { + if (isShowToast) ToastUtils.showToast("回答最多输入10000个字", if (mIsKeyBoardShow) Gravity.CENTER else -1) + return false + } + val draftValue = mViewModel.draftsLiveData.value + if (!draftValue.isNullOrEmpty()) { + return draftValue != answerContent + } + return true + } + private fun getReplaceRealContent(): String { var answerContent = mRichEditor.html for (s in mViewModel.mapImages.keys) { @@ -403,6 +415,12 @@ class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { return answerContent } + private fun checkPostButtonEnable() { + val answerContent = getReplaceRealContent() + val isEnabled = checkData(answerContent, false) + mMenuPost.actionView.alpha = if (isEnabled) 1f else 0.6f + } + override fun handleBackPressed(): Boolean { return if (TextUtils.isEmpty(UserManager.getInstance().token)) { false @@ -417,14 +435,9 @@ class AnswerEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { && !mRichEditor.html.contains(" { - DialogUtils.showNewAlertDialog(binding.root.context, "提示", "是否将此条评论置顶?", "取消", "确认", null, Gravity.CENTER, {}, { + DialogUtils.showNewAlertDialog(binding.root.context, "提示", "是否将此条评论置顶?", "取消", "确认", null, Gravity.CENTER, false, {}, { commentTop(binding.root.context, viewModel, comment, false) }) } "取消置顶" -> { - DialogUtils.showNewAlertDialog(binding.root.context, "提示", "是否将此条评论取消置顶?", "取消", "确认", null, Gravity.CENTER, {}, { + DialogUtils.showNewAlertDialog(binding.root.context, "提示", "是否将此条评论取消置顶?", "取消", "确认", null, Gravity.CENTER, false, {}, { viewModel.updateCommentTop(comment.id ?: "", top = false, isAgain = false) { isSuccess, errorCode -> if (isSuccess) { @@ -433,7 +433,7 @@ abstract class BaseArticleDetailCommentAdapter(context: Context, viewModel.load(LoadType.REFRESH) } else { if (errorCode == 403095) { - DialogUtils.showNewAlertDialog(context, "提示", "当前已有置顶评论,\n是否将此条评论覆盖展示?", "取消", "确认", null, Gravity.CENTER, {}, { + DialogUtils.showNewAlertDialog(context, "提示", "当前已有置顶评论,\n是否将此条评论覆盖展示?", "取消", "确认", null, Gravity.CENTER, false, {}, { commentTop(context, viewModel, comment, true) }) } diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/draft/ArticleDraftFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/article/draft/ArticleDraftFragment.kt index 6245086e2e..fd2187c7f2 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/draft/ArticleDraftFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/draft/ArticleDraftFragment.kt @@ -3,10 +3,13 @@ package com.gh.gamecenter.qa.article.draft import android.app.Activity import android.content.Intent import android.os.Bundle +import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.RecyclerView import com.gh.common.util.EntranceUtils import com.gh.common.util.UrlFilterUtils import com.gh.common.util.checkStoragePermissionBeforeAction +import com.gh.common.view.CustomDividerItemDecoration import com.gh.gamecenter.R import com.gh.gamecenter.baselist.ListFragment import com.gh.gamecenter.baselist.LoadType @@ -89,6 +92,12 @@ class ArticleDraftFragment : ListFragment() { override fun onResponse(response: ResponseBody?) { - mListViewModel.load(LoadType.REFRESH) + val index = mAdapter?.entityList?.indexOf(entity) ?: -1 + if (index >= 0) { + mAdapter?.entityList?.remove(entity) + if (mAdapter?.entityList.isNullOrEmpty()) { + mListViewModel.load(LoadType.REFRESH) + } else { + mAdapter?.notifyItemRemoved(index) + } + } } override fun onFailure(e: HttpException?) { diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/edit/ArticleEditActivity.kt b/app/src/main/java/com/gh/gamecenter/qa/article/edit/ArticleEditActivity.kt index 015d7b27c6..0369558ed5 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/edit/ArticleEditActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/edit/ArticleEditActivity.kt @@ -9,9 +9,9 @@ import android.os.Bundle import android.os.Message import android.text.InputFilter import android.text.TextUtils +import android.view.Gravity import android.view.MenuItem import android.view.View -import android.view.inputmethod.InputMethodManager import android.widget.* import androidx.core.content.ContextCompat import androidx.core.widget.doOnTextChanged @@ -138,8 +138,6 @@ class ArticleEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { //setEditTextInputSpace() mEditTitle.filters = arrayOf(TextHelper.getFilter(50, "标题最多50个字")) - mEditTitle.requestFocus() - mEditTitle.doOnTextChanged { text, start, count, after -> if (text?.contains("\n") == true) { mEditTitle.setText(text.toString().replace("\n", "")) @@ -194,7 +192,7 @@ class ArticleEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { EventBus.getDefault().post(EBReuse(ARTICLE_DRAFT_CHANGE_TAG)) finish() } else { - showDraftFailureDialog() + //showDraftFailureDialog() } } SaveDraftType.AUTO -> { @@ -288,6 +286,7 @@ class ArticleEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { mGameName.isEnabled = false mGameName.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null) } + mEditTitle.requestFocus() } mViewModel.notSelectForum.observe(this, Observer { if (it) showSelectGameDialog() @@ -361,7 +360,6 @@ class ArticleEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { mViewModel.mSelectCommunityData?.icon = mViewModel.draftEntity?.community?.game?.getIcon() mViewModel.mSelectCommunityData?.iconSubscript = mViewModel.draftEntity?.community?.game?.iconSubscript mEditTitle.setText(mViewModel.draftEntity?.title) - mMenuDraft.isVisible = false mGameName.isEnabled = true setGameName() mViewModel.getArticleDraftsContent(mViewModel.draftEntity?.id!!) @@ -373,7 +371,8 @@ class ArticleEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { mViewModel.mSelectCommunityData?.iconSubscript = mViewModel.detailEntity?.community?.game?.iconSubscript setGameName() - mMenuDraft.isVisible = true + //编辑帖子草稿箱入口不存在 + mMenuDraft.isVisible = false mGameName.isEnabled = false mGameName.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null) @@ -391,9 +390,6 @@ class ArticleEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { mRichEditor.setHtml(html, false) try { mRichEditor.scrollTo(0, 10000000) - val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(mRichEditor, InputMethodManager.SHOW_IMPLICIT) - mRichEditor.postDelayed({ mRichEditor.focusEditor() }, 800) } catch (e: Exception) { e.printStackTrace() } @@ -418,70 +414,57 @@ class ArticleEditActivity : BaseRichEditorActivity(), KeyboardHeightObserver { return false } - if (mViewModel.detailEntity != null) { - return if (!TextUtils.isEmpty(mEditTitle.text) - || mRichEditor.html.contains(" MAX_ARTICLE_TEXT_LENGTH) return false + if (detailEntity != null && detailEntity?.title == title && HtmlUtils.stripHtml(detailEntity?.content) == HtmlUtils.stripHtml(content)) return false return true } - /** - * 根据问题标题获取相应标签(标签默认选中) - */ - fun loadTitleTags() { - processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", true)) - mApi - .getQuestionTagsByTitle(mSelectCommunityData?.id, UrlFilterUtils.getFilterQuery("title", title)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Response>() { - override fun onResponse(response: List?) { - titleTags.postValue(response) - processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", false)) - } - - override fun onFailure(e: HttpException?) { - processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", false)) - Utils.toast(getApplication(), R.string.request_failure_normal_hint) - } - }) - } - - /** * 检查图片是否符合规则并上传图片 * @param picPath 图片本地路径 diff --git a/app/src/main/java/com/gh/gamecenter/qa/draft/CommunityDraftWrapperActivity.kt b/app/src/main/java/com/gh/gamecenter/qa/draft/CommunityDraftWrapperActivity.kt index b4dc0764e1..d6c483307c 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/draft/CommunityDraftWrapperActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/draft/CommunityDraftWrapperActivity.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import com.gh.base.BaseActivity_TabLayout import com.gh.gamecenter.qa.article.draft.ArticleDraftFragment import com.gh.gamecenter.qa.answer.draft.AnswerDraftFragment +import com.gh.gamecenter.qa.questions.draft.QuestionDraftFragment class CommunityDraftWrapperActivity : BaseActivity_TabLayout() { @@ -16,13 +17,13 @@ class CommunityDraftWrapperActivity : BaseActivity_TabLayout() { } override fun initFragmentList(fragments: MutableList) { - fragments.add(AnswerDraftFragment()) fragments.add(ArticleDraftFragment()) + fragments.add(QuestionDraftFragment()) } override fun initTabTitleList(tabTitleList: MutableList) { - tabTitleList.add("回答草稿") tabTitleList.add("帖子草稿") + tabTitleList.add("问题草稿") } companion object { diff --git a/app/src/main/java/com/gh/gamecenter/qa/editor/ArticleViewModel.kt b/app/src/main/java/com/gh/gamecenter/qa/editor/ArticleViewModel.kt index 9e6b96ad31..e6bd143144 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/editor/ArticleViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/editor/ArticleViewModel.kt @@ -5,6 +5,7 @@ import androidx.annotation.Keep import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.gh.gamecenter.baselist.ListViewModel +import com.gh.gamecenter.entity.UserEntity import com.gh.gamecenter.manager.UserManager import com.gh.gamecenter.qa.entity.ArticleEntity import com.gh.gamecenter.retrofit.RetrofitManager @@ -36,6 +37,14 @@ class ArticleViewModel(application: Application, private val articleType: Insert } i++ } + + if (articleType == InsertArticleWrapperActivity.ArticleType.MINE_ARTICLE) { + val userInfoEntity = UserManager.getInstance().userInfoEntity + list.forEach { + it.user = UserEntity(userInfoEntity?.icon, userInfoEntity?.name, userInfoEntity?.userId, + auth = userInfoEntity?.auth, badge = userInfoEntity?.badge) + } + } return list } 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 07528712e4..09361c5e1a 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 @@ -40,6 +40,7 @@ class AnswerEntity() : Parcelable { @SerializedName("sequence_id") var sequenceId: String? = null + @SerializedName("brief", alternate = ["content"]) var brief: String? = null @SerializedName("title") @@ -108,6 +109,10 @@ class AnswerEntity() : Parcelable { @Ignore var description: String? = "" + @Ignore + @SerializedName("popular_answer") + var popularAnswer: AnswerEntity? = null + fun getPassVideos(): List { val passVideos = arrayListOf() for (video in videos) { diff --git a/app/src/main/java/com/gh/gamecenter/qa/entity/QuestionDraftEntity.kt b/app/src/main/java/com/gh/gamecenter/qa/entity/QuestionDraftEntity.kt new file mode 100644 index 0000000000..12c7439724 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/qa/entity/QuestionDraftEntity.kt @@ -0,0 +1,20 @@ +package com.gh.gamecenter.qa.entity + +import android.os.Parcelable +import com.gh.gamecenter.entity.CommunityEntity +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class QuestionDraftEntity( + @SerializedName("_id") + var id: String = "", + @SerializedName("community_id") + var communityId:String="", + var bbs: CommunityEntity? = null, + var title: String = "", + var description: String = "", + var images: ArrayList = arrayListOf(), + var videos: ArrayList = arrayListOf(), + var tags: List = arrayListOf() +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/entity/QuestionsDetailEntity.kt b/app/src/main/java/com/gh/gamecenter/qa/entity/QuestionsDetailEntity.kt index 4cbceaa194..9964c94d11 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/entity/QuestionsDetailEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/entity/QuestionsDetailEntity.kt @@ -29,7 +29,11 @@ data class QuestionsDetailEntity(var id: String? = null, var me: MeEntity = MeEntity(), @SerializedName("follow_count") private var followCount: Int = 0, - var user: UserEntity = UserEntity()) : Parcelable { + var user: UserEntity = UserEntity(), + //提交问题用 + @SerializedName("draft_id") + var draftId: String = "" +) : Parcelable { fun getFollowCount(): Int { return if (followCount == 0) 1 else { diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftActivity.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftActivity.kt new file mode 100644 index 0000000000..349eee17ab --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftActivity.kt @@ -0,0 +1,17 @@ +package com.gh.gamecenter.qa.questions.draft + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.gh.common.util.EntranceUtils +import com.gh.gamecenter.NormalActivity + +class QuestionDraftActivity : NormalActivity() { + + companion object { + fun getIntent(context: Context): Intent { + val bundle = Bundle() + return getTargetIntent(context, QuestionDraftActivity::class.java, QuestionDraftFragment::class.java, bundle) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftAdapter.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftAdapter.kt new file mode 100644 index 0000000000..7cd8821a95 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftAdapter.kt @@ -0,0 +1,71 @@ +package com.gh.gamecenter.qa.questions.draft + +import android.content.Context +import android.text.TextUtils +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.gh.base.BaseRecyclerViewHolder +import com.gh.common.constant.ItemViewType +import com.gh.common.util.DialogUtils +import com.gh.gamecenter.R +import com.gh.gamecenter.adapter.viewholder.FooterViewHolder +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.databinding.CommunityQuestionDraftItemBinding +import com.gh.gamecenter.qa.entity.QuestionDraftEntity + +class QuestionDraftAdapter(val context: Context, val mViewModel: QuestionDraftViewModel?,private val selectCallback: (QuestionDraftEntity) -> Unit) : ListAdapter(context) { + + override fun areItemsTheSame(oldItem: QuestionDraftEntity, newItem: QuestionDraftEntity): Boolean { + return !TextUtils.isEmpty(oldItem.id) && oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: QuestionDraftEntity, newItem: QuestionDraftEntity): Boolean { + return oldItem == newItem + } + + override fun getItemViewType(position: Int): Int { + return if (position == itemCount - 1) { + ItemViewType.ITEM_FOOTER + } else 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_question_draft_item, parent, false) + QuestionDraftViewHolder(CommunityQuestionDraftItemBinding.bind(view)) + } + } + } + + override fun getItemCount(): Int { + return if (mEntityList == null || mEntityList.isEmpty()) 0 else mEntityList.size + FOOTER_ITEM_COUNT + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is QuestionDraftViewHolder) { + val entity = mEntityList[position] + holder.binding.data = entity + holder.binding.articleDraftDelete.setOnClickListener { + DialogUtils.showAlertDialog(mContext, "警告", "确定要删除问题草稿吗?删除之后不可恢复", + "确定", "取消", DialogUtils.ConfirmListener { + mViewModel?.deleteDraft(entity.id) + }, null) + } + holder.itemView.setOnClickListener { + selectCallback.invoke(entity) + } + } else if (holder is FooterViewHolder) { + holder.initItemPadding() + holder.initFooterViewHolder(mIsLoading, isNetworkError, mIsOver, R.string.ask_loadover_hint) + } + } + + class QuestionDraftViewHolder(val binding: CommunityQuestionDraftItemBinding) : BaseRecyclerViewHolder(binding.root) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftFragment.kt new file mode 100644 index 0000000000..1921561f7a --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftFragment.kt @@ -0,0 +1,94 @@ +package com.gh.gamecenter.qa.questions.draft + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import androidx.core.content.ContextCompat +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.ToastUtils +import com.gh.common.util.checkStoragePermissionBeforeAction +import com.gh.common.util.viewModelProvider +import com.gh.common.view.CustomDividerItemDecoration +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.baselist.LoadType +import com.gh.gamecenter.eventbus.EBReuse +import com.gh.gamecenter.qa.article.edit.ArticleEditActivity +import com.gh.gamecenter.qa.draft.CommunityDraftWrapperActivity +import com.gh.gamecenter.qa.entity.ArticleDetailEntity +import com.gh.gamecenter.qa.entity.ArticleDraftEntity +import com.gh.gamecenter.qa.entity.QuestionDraftEntity +import com.gh.gamecenter.qa.questions.edit.QuestionEditActivity +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode + +class QuestionDraftFragment : ListFragment() { + + var mAdapter: QuestionDraftAdapter? = null + lateinit var mViewModel: QuestionDraftViewModel + + override fun provideListAdapter(): ListAdapter<*> { + return mAdapter ?: QuestionDraftAdapter(requireContext(), mViewModel) { + if (activity is CommunityDraftWrapperActivity) { + checkStoragePermissionBeforeAction { + val intent = QuestionEditActivity.getDraftIntent(requireContext(), it) + startActivity(intent) + } + } else { + checkStoragePermissionBeforeAction { + val intent = Intent() + intent.putExtra(QuestionDraftEntity::class.java.simpleName, it) + requireActivity().setResult(Activity.RESULT_OK, intent) + requireActivity().finish() + } + } + }.apply { + mAdapter = this + } + } + + override fun provideListViewModel(): QuestionDraftViewModel { + mViewModel = viewModelProvider() + return mViewModel + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (activity is QuestionDraftActivity) { + setNavigationTitle("问题草稿") + } + mViewModel.deleteDraftSuccess.observe(this, Observer { pair -> + if (pair.second) { + val draftId = pair.first + val index = mAdapter?.entityList?.indexOfFirst { it.id == draftId } ?: -1 + if (index >= 0) { + mAdapter?.entityList?.removeAt(index) + if (mAdapter?.entityList.isNullOrEmpty()) { + mListViewModel.load(LoadType.REFRESH) + } else { + mAdapter?.notifyItemRemoved(index) + } + + ToastUtils.showToast("删除成功") + } + + } + }) + } + + override fun getItemDecoration(): RecyclerView.ItemDecoration? { + val insetDivider = ContextCompat.getDrawable(requireContext(), R.drawable.divider_item_line_space_16) + val itemDecoration = CustomDividerItemDecoration(requireContext(), notDecorateTheLastItem = true) + itemDecoration.setDrawable(insetDivider!!) + return itemDecoration + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEventMainThread(reuse: EBReuse) { + if (QuestionEditActivity.QUESTION_DRAFT_CHANGE_TAG == reuse.type) { + mBaseHandler.postDelayed({ mListViewModel.load(LoadType.REFRESH) }, 100) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftViewModel.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftViewModel.kt new file mode 100644 index 0000000000..6b720870cf --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/draft/QuestionDraftViewModel.kt @@ -0,0 +1,46 @@ +package com.gh.gamecenter.qa.questions.draft + +import android.annotation.SuppressLint +import android.app.Application +import androidx.lifecycle.MutableLiveData +import com.gh.common.util.observableToMain +import com.gh.gamecenter.baselist.ListViewModel +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.qa.entity.QuestionDraftEntity +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.halo.assistant.HaloApp +import io.reactivex.Observable +import okhttp3.ResponseBody +import retrofit2.HttpException + +class QuestionDraftViewModel(application: Application) : ListViewModel(application) { + val deleteDraftSuccess = MutableLiveData>() + private val api = RetrofitManager.getInstance(HaloApp.getInstance().application).api + + override fun provideDataObservable(page: Int): Observable> { + return api.getQuestionDrafts(UserManager.getInstance().userId) + } + + override fun mergeResultLiveData() { + mResultLiveData.addSource(mListLiveData) { mResultLiveData.postValue(it) } + } + + @SuppressLint("CheckResult") + fun deleteDraft(draftId: String) { + api.deleteQuestionDraft(UserManager.getInstance().userId, draftId) + .compose(observableToMain()) + .subscribe(object :Response(){ + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + deleteDraftSuccess.postValue(Pair(draftId, true)) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + deleteDraftSuccess.postValue(Pair(draftId, false)) + } + }) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditActivity.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditActivity.kt index c61d2b148c..9d56d6a714 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditActivity.kt @@ -9,24 +9,18 @@ import android.graphics.Color import android.graphics.LinearGradient import android.graphics.Shader import android.os.Bundle +import android.os.Message import android.text.Editable +import android.text.TextUtils import android.text.TextWatcher -import android.view.KeyEvent -import android.view.MenuItem -import android.view.View -import android.view.Window +import android.view.* import android.widget.EditText -import android.widget.ProgressBar -import android.widget.RelativeLayout import androidx.core.content.ContextCompat import androidx.core.widget.addTextChangedListener import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.gh.base.ToolBarActivity -import com.gh.base.fragment.BaseDialogWrapperFragment import com.gh.base.fragment.WaitingDialogFragment import com.gh.common.AppExecutor import com.gh.common.util.* @@ -39,17 +33,23 @@ import com.gh.gamecenter.entity.CommunityEntity import com.gh.gamecenter.entity.MyVideoEntity import com.gh.gamecenter.entity.NotificationUgc import com.gh.gamecenter.entity.Permissions +import com.gh.gamecenter.eventbus.EBReuse import com.gh.gamecenter.mvvm.Status -import com.gh.gamecenter.qa.article.edit.ArticleSelectGameAdapter -import com.gh.gamecenter.qa.article.edit.ArticleTagsSelectFragment +import com.gh.gamecenter.qa.answer.edit.AnswerEditActivity +import com.gh.gamecenter.qa.article.draft.ArticleDraftActivity +import com.gh.gamecenter.qa.article.edit.ArticleEditActivity import com.gh.gamecenter.qa.dialog.ChooseForumDialogFragment import com.gh.gamecenter.qa.editor.VideoActivity import com.gh.gamecenter.qa.entity.CommunityVideoEntity +import com.gh.gamecenter.qa.entity.QuestionDraftEntity import com.gh.gamecenter.qa.entity.QuestionsDetailEntity +import com.gh.gamecenter.qa.questions.draft.QuestionDraftActivity import com.gh.gamecenter.qa.questions.edit.pic.QuestionsEditPicAdapter import com.gh.gamecenter.qa.questions.edit.tip.QuestionTitleTipAdapter import com.lightgame.utils.Util_System_Keyboard +import com.lightgame.utils.Utils import com.zhihu.matisse.Matisse +import org.greenrobot.eventbus.EventBus import org.json.JSONObject import kotlin.math.abs @@ -66,10 +66,12 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { private var mUploadImageCancelDialog: Dialog? = null private var mKeyboardHeightProvider: KeyboardHeightProvider? = null private lateinit var picAdapter: QuestionsEditPicAdapter + private lateinit var mMenuDraft: MenuItem private lateinit var mMenuPost: MenuItem private var mIsExtendedKeyboardShow = false private var mOffset = 0 private var mTagsSelectFragment: TagsSelectFragment? = null + private var mPostDraftsCount: Int = 0 private val mSaveTitleKey = "title" private val mSaveContentKey = "content" @@ -96,6 +98,25 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { duration = RichEditor.formatVideoDuration(videoEntity.length), status = videoEntity.status)) } + } else if (requestCode == QUESTION_DRAFT_REQUEST_CODE && resultCode == RESULT_OK) { + val draftEntity = data?.getParcelableExtra(QuestionDraftEntity::class.java.simpleName) + if (draftEntity != null) { + mViewModel.questionDraftEntity = draftEntity + setQuestionDraft(draftEntity) + mViewModel.getQuestionDraftContent(draftEntity.id) + } + } + } + + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + if (msg.what == 1) { + if (mViewModel.communityEntity != null + && !mViewModel.title.isNullOrEmpty() + && !mViewModel.content.isNullOrEmpty()) { + mViewModel.saveQuestionDraft(SaveDraftType.AUTO) + } + mBaseHandler.sendEmptyMessageDelayed(1, SAVE_DRAFTS_INTERVAL_TIME.toLong()) } } @@ -104,6 +125,7 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { setToolbarMenu(R.menu.menu_question_post) mToolbar.navigationIcon = null + mMenuDraft = mToolbar.menu.findItem(R.id.menu_draft) mMenuPost = mToolbar.menu.findItem(R.id.menu_question_post) mViewModel = ViewModelProviders.of(this).get(QuestionEditViewModel::class.java) if (savedInstanceState != null) { @@ -127,24 +149,25 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { if (intent != null) { val communityEntity = intent.getParcelableExtra(CommunityEntity::class.java.simpleName) val detailEntity = intent.getParcelableExtra(QuestionsDetailEntity::class.java.simpleName) - if (detailEntity != null) { // 问题编辑 - mViewModel.questionEntity = detailEntity - mViewModel.communityEntity = detailEntity.community - mViewModel.communityEntity?.icon = detailEntity.community.game?.getIcon() - mViewModel.communityEntity?.iconSubscript = detailEntity.community.game?.iconSubscript - setForumName() - mViewModel.content = detailEntity.description - mViewModel.picList.postValue(ArrayList(detailEntity.images)) - mViewModel.isModeratorPatch = intent.getBooleanExtra(EntranceUtils.KEY_QUESTION_MODERATOR_PATCH, false) - val videos = detailEntity.videos - if (videos.isNotEmpty()) mViewModel.videoLiveData.postValue(detailEntity.videos[0]) - if (mViewModel.title.isNullOrEmpty()) mViewModel.title = detailEntity.title - } else { // 新增问题 - var searchKey = intent.getStringExtra(EntranceUtils.KEY_QUESTIONS_SEARCH_KEY) - if (!searchKey.isNullOrEmpty() && searchKey.length > QuestionEditViewModel.QUESTION_TITLE_MAX_LENGTH) - searchKey = searchKey.substring(0, QuestionEditViewModel.QUESTION_TITLE_MAX_LENGTH) - if (mViewModel.title.isNullOrEmpty()) mViewModel.title = searchKey - mViewModel.isFromSearch = intent.getBooleanExtra(QuestionEditViewModel.QUESTION_FORM_SEARCH, false) + val draftEntity = intent.getParcelableExtra(QuestionDraftEntity::class.java.simpleName) + when { + detailEntity != null -> { // 问题编辑 + setPatchContent(detailEntity) + } + draftEntity != null -> { //草稿编辑 + mViewModel.questionDraftEntity = draftEntity + setQuestionDraft(draftEntity) + mViewModel.getQuestionDraftContent(draftEntity.id) + mBaseHandler.sendEmptyMessageDelayed(1, SAVE_DRAFTS_INTERVAL_TIME.toLong()) + } + else -> { // 新增问题 + var searchKey = intent.getStringExtra(EntranceUtils.KEY_QUESTIONS_SEARCH_KEY) + if (!searchKey.isNullOrEmpty() && searchKey.length > QuestionEditViewModel.QUESTION_TITLE_MAX_LENGTH) + searchKey = searchKey.substring(0, QuestionEditViewModel.QUESTION_TITLE_MAX_LENGTH) + if (mViewModel.title.isNullOrEmpty()) mViewModel.title = searchKey + mViewModel.isFromSearch = intent.getBooleanExtra(QuestionEditViewModel.QUESTION_FORM_SEARCH, false) + mBaseHandler.sendEmptyMessageDelayed(1, SAVE_DRAFTS_INTERVAL_TIME.toLong()) + } } if (communityEntity != null) { @@ -152,7 +175,6 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { setForumName() mBinding.chooseForumTv.isEnabled = false mBinding.chooseForumTv.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null) - if (!mViewModel.isFromSearch) mViewModel.checkQuestionDraft() } } @@ -195,6 +217,7 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { mBinding.questionseditContent.filters = arrayOf(TextHelper.getFilter(300, "内容最多300个字")) mBinding.questionseditContent.addTextChangedListener { mBinding.editorTextNumTv.text = "${mBinding.questionseditContent.text.length}/300" + checkPostButtonEnable() } mBinding.questionseditContent.setOnTouchListener { v, event -> closeExtendedKeyboard() @@ -236,6 +259,35 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { observeData() } + private fun setPatchContent(detailEntity: QuestionsDetailEntity) { + mMenuDraft.isVisible = false + mViewModel.questionEntity = detailEntity + mViewModel.communityEntity = detailEntity.community + mViewModel.communityEntity?.icon = detailEntity.community.game?.getIcon() + mViewModel.communityEntity?.iconSubscript = detailEntity.community.game?.iconSubscript + mViewModel.content = detailEntity.description + mViewModel.picList.postValue(ArrayList(detailEntity.images)) + mViewModel.isModeratorPatch = intent.getBooleanExtra(EntranceUtils.KEY_QUESTION_MODERATOR_PATCH, false) + val videos = detailEntity.videos + if (videos.isNotEmpty()) mViewModel.videoLiveData.postValue(detailEntity.videos[0]) + if (mViewModel.title.isNullOrEmpty()) mViewModel.title = detailEntity.title + setForumName() + } + + private fun setQuestionDraft(draftEntity: QuestionDraftEntity) { + mViewModel.communityEntity = draftEntity.bbs + mViewModel.communityEntity?.icon = draftEntity.bbs?.game?.getIcon() + mViewModel.communityEntity?.iconSubscript = draftEntity.bbs?.game?.iconSubscript + mViewModel.title = draftEntity.title + mViewModel.content = draftEntity.description + mViewModel.picList.postValue(draftEntity.images) + val videos = draftEntity.videos + if (videos.isNotEmpty()) mViewModel.videoLiveData.postValue(videos[0]) + mBinding.questionseditTitle.setText(mViewModel.title) + mBinding.questionseditContent.setText(mViewModel.content) + setForumName() + } + private fun observeData() { mViewModel.moderatorPostLiveData.observe(this, Observer { if (it?.status == Status.SUCCESS) { @@ -269,6 +321,41 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { toast(R.string.post_failure_hint) } }) + mViewModel.postQuestionDrafts.observe(this, Observer { pair -> + if (pair != null) { + when (pair.first) { + SaveDraftType.EXIT -> { + if (pair.second) { + Utils.toast(this, "问题已保存到草稿箱") + EventBus.getDefault().post(EBReuse(ArticleEditActivity.ARTICLE_DRAFT_CHANGE_TAG)) + finish() + } + } + SaveDraftType.AUTO -> { + if (pair.second) { + if (mPostDraftsCount >= AnswerEditActivity.SAVE_DRAFTS_TOAST_COUNT) { + mPostDraftsCount = 0 + Utils.toast(this, "问题已保存到草稿箱") + } else { + mPostDraftsCount++ + } + } + } + SaveDraftType.SKIP -> { + if (pair.second) { + Utils.toast(this, "问题已保存到草稿箱") + startActivityForResult(ArticleDraftActivity.getIntent(this), ArticleEditActivity.ARTICLE_DRAFT_REQUEST_CODE) + } else { + Utils.toast(this, "问题草稿保存失败") + } + } + } + } + }) + mViewModel.questionDraftsContent.observe(this, Observer { + mViewModel.questionDraftEntity = it + setQuestionDraft(it) + }) // Process dialog mViewModel.processDialog.observe(this, Observer { it -> @@ -419,23 +506,30 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { } override fun onMenuItemClick(menuItem: MenuItem?): Boolean { - if (mViewModel.isModeratorPatch) { - if (checkSameFromQuestionData()) { - toast("内容没有变化") + if (menuItem?.itemId == R.id.menu_question_post) { + if (mViewModel.isModeratorPatch) { + if (checkSameFromQuestionData()) { + toast("内容没有变化") + } else { + mViewModel.selectedTags.addAll(mViewModel.questionEntity?.tags!!) + DialogUtils.showAlertDialog(this, "修改问题", + if (mViewModel.questionEntity!!.me.moderatorPermissions.updateQuestion == Permissions.REPORTER) + "你的操作将提交给小编审核,确定提交吗?" else "你的操作将立即生效,确定提交吗?(你的管理权限为:高级)", + "确定", "取消", DialogUtils.ConfirmListener { + mViewModel.uploadPicAndPatchQuestion(false) + }, null) + } } else { - mViewModel.selectedTags.addAll(mViewModel.questionEntity?.tags!!) - DialogUtils.showAlertDialog(this, "修改问题", - if (mViewModel.questionEntity!!.me.moderatorPermissions.updateQuestion == Permissions.REPORTER) - "你的操作将提交给小编审核,确定提交吗?" else "你的操作将立即生效,确定提交吗?(你的管理权限为:高级)", - "确定", "取消", DialogUtils.ConfirmListener { - mViewModel.uploadPicAndPatchQuestion(false) - }, null) + if (mViewModel.checkTitleAndLoadTitleTag()) { + mTagsSelectFragment?.postQuestion() + } } - } else { - if (mViewModel.checkTitleAndLoadTitleTag()) { - mTagsSelectFragment?.postQuestion() + } else if (menuItem?.itemId == R.id.menu_draft) { + if (checkDraft(SaveDraftType.SKIP)) { + startActivityForResult(QuestionDraftActivity.getIntent(this), QUESTION_DRAFT_REQUEST_CODE) } } + return false } @@ -466,7 +560,6 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { mViewModel.questionEntity?.community?.name = it.name } setForumName() - if (!mViewModel.isFromSearch) mViewModel.checkQuestionDraft() mBinding.vm = mViewModel } } @@ -513,8 +606,6 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { } // 需要检查的内容,其中任意一个不为空都要打开提示弹窗 val imgList = mViewModel.picList.value - val title = mBinding.questionseditTitle.text.toString().trim() - val content = mBinding.questionseditContent.text.toString().trim() if (mViewModel.isModeratorPatch) { if (checkSameFromQuestionData()) { @@ -524,21 +615,62 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { , "确定退出修改?已编辑的内容将丢失" , "继续编辑", " 退出", null) { finish() } return true - } else if (imgList != null && imgList.size > 0 || title.isNotEmpty() || content.isNotEmpty()) { - if (mViewModel.questionEntity == null && !mViewModel.isFromSearch) { - mViewModel.saveQuestionDraft() - toast("问题草稿已保存") + } + + //问题发布 + if (mViewModel.questionEntity == null && mViewModel.questionDraftEntity == null) { + if (mViewModel.communityEntity != null && (!imgList.isNullOrEmpty() || !mViewModel.title.isNullOrEmpty() || !mViewModel.content.isNullOrEmpty())) { + DialogUtils.showNewAlertDialog(this, "提示", "是否保存内容再退出?", "不保存", "保存并退出", Gravity.CENTER, true, { + finish() + }, { + mViewModel.saveQuestionDraft(SaveDraftType.EXIT) + }) + return true + } + } + + //问题编辑,需要判断是否修改过 + if (mViewModel.questionEntity != null) { + return if (mViewModel.questionEntity?.community?.id != mViewModel.communityEntity?.id + || mViewModel.questionEntity?.title != mViewModel.title + || mViewModel.questionEntity?.description != mViewModel.content) { + showBackDialog() + true + } else false + } + + return !checkDraft(SaveDraftType.EXIT) + } + + private fun checkDraft(saveType: SaveDraftType): Boolean { + val draftEntity = mViewModel.questionDraftEntity ?: return true + if (draftEntity.title.isEmpty() && draftEntity.description.isEmpty()) return true + if (saveType == SaveDraftType.SKIP) { + //判断是否修改了草稿,修改了需自动保存无需提示 + if (draftEntity.bbs?.id != mViewModel.communityEntity?.id + || draftEntity.title != mViewModel.title + || draftEntity.description != mViewModel.content) { + mViewModel.saveQuestionDraft(SaveDraftType.AUTO) + return true + } + } else if (saveType == SaveDraftType.EXIT) { + //退出页面需判断是否修改了草稿,修改了需弹窗提示 + if (draftEntity.bbs?.id != mViewModel.communityEntity?.id + || draftEntity.title != mViewModel.title + || draftEntity.description != mViewModel.content) { + showBackDialog() return false } - - DialogUtils.showCancelAlertDialog(this, "提示" - , if (mViewModel.questionEntity == null) "确定放弃提问吗?" else "确定放弃修改吗?" - , "再想想", " 放弃", null) { finish() } - return true - } else if (mViewModel.questionEntity == null && !mViewModel.isFromSearch) { - mViewModel.cleanCurrentCommunityDraft() } - return false + return true + } + + private fun showBackDialog() { + DialogUtils.showNewAlertDialog(this, "提示", "是否保存修改内容用于下次编辑?", "不保存", "保存并退出", Gravity.CENTER, true,{ + finish() + }, { + mViewModel.saveQuestionDraft(SaveDraftType.EXIT) + }) } private fun checkSameFromQuestionData(): Boolean { @@ -594,6 +726,9 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { companion object { const val QUESTION_POSTED_TAG = "QUESTION_POSTED_TAG" + const val QUESTION_DRAFT_REQUEST_CODE = 105 + const val QUESTION_DRAFT_CHANGE_TAG = "ANSWER_DRAFT_CHANGE_TAG" + const val SAVE_DRAFTS_INTERVAL_TIME = 15000 // searchKey 补充到标题(新增问题) fun getIntent(context: Context, searchKey: String?): Intent { @@ -630,5 +765,19 @@ class QuestionEditActivity : ToolBarActivity(), KeyboardHeightObserver { return intent } + //编辑草稿 + @JvmStatic + fun getDraftIntent(context: Context, questionDraftEntity: QuestionDraftEntity): Intent { + val intent = Intent(context, QuestionEditActivity::class.java) + intent.putExtra(QuestionDraftEntity::class.java.simpleName, questionDraftEntity) + return intent + } + + } + + enum class SaveDraftType { + EXIT, // 退出时保存 + AUTO, // 自动保存 + SKIP // 跳转至草稿箱时保存 } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditViewModel.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditViewModel.kt index 37cfee9710..34bae5b4d1 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/edit/QuestionEditViewModel.kt @@ -1,5 +1,6 @@ package com.gh.gamecenter.qa.questions.edit +import android.annotation.SuppressLint import android.app.Application import android.net.Uri import android.provider.MediaStore @@ -12,11 +13,16 @@ import com.gh.common.util.* import com.gh.gamecenter.R import com.gh.gamecenter.entity.CommunityEntity import com.gh.gamecenter.entity.Permissions +import com.gh.gamecenter.entity.User import com.gh.gamecenter.eventbus.EBReuse import com.gh.gamecenter.manager.UserManager import com.gh.gamecenter.mvvm.Resource +import com.gh.gamecenter.qa.article.edit.ArticleEditActivity +import com.gh.gamecenter.qa.entity.ArticleDraftEntity import com.gh.gamecenter.qa.entity.CommunityVideoEntity +import com.gh.gamecenter.qa.entity.QuestionDraftEntity import com.gh.gamecenter.qa.entity.QuestionsDetailEntity +import com.gh.gamecenter.retrofit.BiResponse import com.gh.gamecenter.retrofit.Response import com.gh.gamecenter.retrofit.RetrofitManager import com.gh.gamecenter.retrofit.service.ApiService @@ -50,6 +56,8 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica val moderatorPostLiveData = MediatorLiveData>() val videoLiveData = MutableLiveData() val notSelectForum = MutableLiveData() + val postQuestionDrafts = MediatorLiveData>() + val questionDraftsContent = MutableLiveData() var uploadImageSubscription: Disposable? = null @@ -58,9 +66,10 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica var content: String? = "" // picList 纯粹用于展示,可以包含本地和网络地址的图片 - val picList = MediatorLiveData>() + val picList = MediatorLiveData>() var defaultTags: MutableList = ArrayList() var questionEntity: QuestionsDetailEntity? = null + var questionDraftEntity: QuestionDraftEntity? = null val selectedTags: MutableList = ArrayList() val selectedTagsChange = MediatorLiveData() @@ -118,6 +127,8 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica Utils.toast(getApplication(), "标题至少${QUESTION_TITLE_MIN_LENGTH}个字") return false } + if (questionEntity != null && questionEntity?.title == title && questionEntity?.description == content) return false + if (TextUtils.isEmpty(communityEntity?.id) || TextUtils.isEmpty(communityEntity?.name)) { Utils.toast(getApplication(), "论坛不能为空") notSelectForum.postValue(true) @@ -140,31 +151,10 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica // 检查标题长度限制 title?.trim() if (title!!.length < QUESTION_TITLE_MIN_LENGTH) return false + if (questionEntity != null && questionEntity?.title == title && questionEntity?.description == content) return false return true } - /** - * 根据问题标题获取相应标签(标签默认选中) - */ - private fun loadTitleTags() { - processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", true)) - mApiService - .getQuestionTagsByTitle(communityEntity?.id, UrlFilterUtils.getFilterQuery("title", title)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Response>() { - override fun onResponse(response: List?) { - titleTags.postValue(response) - processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", false)) - } - - override fun onFailure(e: HttpException?) { - processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", false)) - Utils.toast(getApplication(), R.string.request_failure_normal_hint) - } - }) - } - /** * 选择动作(选中标签/移除已选中的标签) */ @@ -353,7 +343,9 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica entity.images = successfullyUploadedPicList val video: CommunityVideoEntity? = videoLiveData.value if (video != null) entity.videos = arrayListOf(video) - + if (!questionDraftEntity?.id.isNullOrEmpty()) { + entity.draftId = questionDraftEntity?.id ?: "" + } val observable = if (questionEntity != null) { if (!isModeratorPatch) { questionEntity?.images = entity.images @@ -384,16 +376,18 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica override fun onResponse(response: ResponseBody?) { processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", false)) MtaHelper.onEvent("发表问题", "提交成功", communityEntity?.name) - val data = response?.string() + val data = response?.string() ?: "" postLiveData.postValue(Resource.success(data)) EventBus.getDefault().post(EBReuse(QuestionEditActivity.QUESTION_POSTED_TAG)) - if (questionEntity == null && !isFromSearch) cleanCurrentCommunityDraft() if (questionEntity == null) { tryWithDefaultCatch { EnergyTaskHelper.postEnergyTask("post_question", JSONObject(data).optString("_id")) } } + if (!questionDraftEntity?.id.isNullOrEmpty()) { + EventBus.getDefault().post(EBReuse(QuestionEditActivity.QUESTION_DRAFT_CHANGE_TAG)) + } } override fun onFailure(e: HttpException?) { @@ -440,65 +434,63 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica }) } - fun cleanCurrentCommunityDraft() { - val draftKey = getDraftKey() - if (draftKey.isNullOrEmpty()) { - //Utils.toast(getApplication(), "删除草稿失败") - return + @SuppressLint("CheckResult") + fun saveQuestionDraft(saveType: QuestionEditActivity.SaveDraftType) { + val body = getQuestionBody() + val observable = if (!questionDraftEntity?.id.isNullOrEmpty()) { + mApiService.updateQuestionDraft(UserManager.getInstance().userId, questionDraftEntity?.id, body) + } else { + mApiService.addQuestionDraft(UserManager.getInstance().userId, body) } - SPUtils.remove(draftKey) + observable + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + val string = data.string() + if (!string.isNullOrEmpty() && questionDraftEntity?.id.isNullOrEmpty()) { + if (questionDraftEntity == null) questionDraftEntity = QuestionDraftEntity() + questionDraftEntity?.id = JSONObject(string).getString("_id") + } + postQuestionDrafts.postValue(Pair(saveType, true)) + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + postQuestionDrafts.postValue(Pair(saveType, false)) + } + }) } - fun saveQuestionDraft() { - val draftKey = getDraftKey() - if (draftKey.isNullOrEmpty()) { - Utils.toast(getApplication(), "保存草稿失败") - return - } - - val draftJson = JSONObject() - draftJson.put("title", title) - draftJson.put("content", content) - draftJson.put("images", JSONArray(picList.value)) - SPUtils.setString(draftKey, draftJson.toString()) + private fun getQuestionBody(): RequestBody { + val draftEntity = QuestionDraftEntity( + communityId = communityEntity?.id ?: "", + tags = selectedTags.toList(), + images = picList.value ?: arrayListOf(), + videos = if (videoLiveData.value != null) arrayListOf(videoLiveData.value!!) else arrayListOf(), + title = title ?: "", + description = content ?: "" + ) + return draftEntity.toRequestBody() } - fun checkQuestionDraft() { - val draftKey = getDraftKey() - if (draftKey.isNullOrEmpty()) { - Utils.toast(getApplication(), "获取草稿失败") - return - } + @SuppressLint("CheckResult") + fun getQuestionDraftContent(draftId: String) { + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("加载中...", true)) + mApiService.getQuestionDraft(UserManager.getInstance().userId, draftId) + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: QuestionDraftEntity) { + questionDraftsContent.postValue(data) + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("加载中...", false)) + } - val draft = SPUtils.getString(draftKey, "") - if (draft.isNullOrEmpty()) return - val draftJson = JSONObject(draft) - - val draftTitle = draftJson.optString("title") - if (draftTitle.isNotEmpty()) title = draftTitle - - val draftContent = draftJson.optString("content") - if (draftContent.isNotEmpty()) content = draftContent - - val draftImages = draftJson.optJSONArray("images") - if (draftImages != null) { - val images = ArrayList() - for (i in 0 until draftImages.length()) { - val img = draftImages.getString(i) - if (img.isNotEmpty() && File(img).exists()) { - images.add(img) - } - } - if (images.isNotEmpty()) picList.postValue(images) - } + override fun onFailure(exception: Exception) { + super.onFailure(exception) + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("加载中...", false)) + } + }) } - private fun getDraftKey(): String? { - if (communityEntity?.id.isNullOrEmpty()) return null - return QUESTION_DRAFT_KEY + communityEntity?.id - } - - companion object { const val QUESTION_TAG_MAX_COUNT = 5 const val PIC_MAX_AMOUNT = 30 diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/edit/TagsSelectFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/edit/TagsSelectFragment.kt index 710c57be20..b6429d9921 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/questions/edit/TagsSelectFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/edit/TagsSelectFragment.kt @@ -223,28 +223,25 @@ class TagsSelectFragment : BaseFragment() { // 添加标签 val mDefaultTag = mViewModel?.defaultTags ?: ArrayList() - if (mViewModel?.questionEntity == null) { - val mTitleTag = (mViewModel?.titleTags?.value ?: ArrayList()) as MutableList - // 添加默认标签(如果标题标签与默认标签重复则选中) - for (s in mDefaultTag.reversed()) { - val contains = mTitleTag.contains(s) - addTag(s, contains) - if (contains) mTitleTag.remove(s) + val tags = when { + mViewModel?.questionEntity != null -> { + mViewModel?.questionEntity?.tags ?: arrayListOf() } - // 未匹配到默认标签的默认选中在前排 - for (s in mTitleTag) { - addTag(s, true) + mViewModel?.questionDraftEntity != null -> { + mViewModel?.questionDraftEntity?.tags ?: arrayListOf() } - } else { - val tags = mViewModel?.questionEntity?.tags ?: ArrayList() - for (s in mDefaultTag) { - if (!tags.contains(s)) - addTag(s, false) - } - for (s in tags.reversed()) { - addTag(s, true) + else -> { + arrayListOf() } } + + for (s in mDefaultTag) { + if (!tags.contains(s)) + addTag(s, false) + } + for (s in tags.reversed()) { + addTag(s, true) + } } private fun showAddTagDialog() { 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 24c20a1bac..88c1c5bddd 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 @@ -100,6 +100,7 @@ import com.gh.gamecenter.qa.entity.CommunitySelectEntity; import com.gh.gamecenter.qa.entity.CommunitySelectOpenEntity; import com.gh.gamecenter.qa.entity.EditorInsertDefaultEntity; import com.gh.gamecenter.qa.entity.InviteEntity; +import com.gh.gamecenter.qa.entity.QuestionDraftEntity; import com.gh.gamecenter.qa.entity.QuestionHistoryDetailEntity; import com.gh.gamecenter.qa.entity.QuestionHistoryEntity; import com.gh.gamecenter.qa.entity.Questions; @@ -108,6 +109,7 @@ import com.gh.gamecenter.qa.entity.QuestionsIndexEntity; import com.gh.gamecenter.qa.entity.SearchHottestEntity; import com.gh.gamecenter.qa.entity.SearchNewestEntity; import com.gh.gamecenter.qa.entity.SuggestedFollowEntity; +import com.gh.gamecenter.retrofit.Response; import com.google.gson.JsonObject; import java.util.ArrayList; @@ -2808,4 +2810,34 @@ public interface ApiService { */ @GET("shop/orders/roll_notices") Single> getRollNotices(); + + /** + * 新增一个问题草稿 + */ + @POST("users/{user_id}/question_drafts") + Single addQuestionDraft(@Path("user_id") String userId, @Body RequestBody body); + + /** + * 获取单个问题草稿 + */ + @GET("users/{user_id}/question_drafts/{draft_id}") + Single getQuestionDraft(@Path("user_id") String userId, @Path("draft_id") String draftId); + + /** + * 修改一个问题草稿 + */ + @POST("users/{user_id}/question_drafts/{draft_id}") + Single updateQuestionDraft(@Path("user_id") String userId, @Path("draft_id") String draftId, @Body RequestBody body); + + /** + * 获取问题草稿列表 + */ + @GET("users/{user_id}/question_drafts") + Observable> getQuestionDrafts(@Path("user_id") String userId); + + /** + * 删除单个问题草稿 + */ + @DELETE("users/{user_id}/question_drafts/{draft_id}") + Observable deleteQuestionDraft(@Path("user_id") String userId, @Path("draft_id") String draftId); } \ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/ic_alert_dialog_close.png b/app/src/main/res/drawable-xxhdpi/ic_alert_dialog_close.png new file mode 100644 index 0000000000..8a4adefd49 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_alert_dialog_close.png differ diff --git a/app/src/main/res/drawable/divider_item_line_space_16.xml b/app/src/main/res/drawable/divider_item_line_space_16.xml new file mode 100644 index 0000000000..3343904cb8 --- /dev/null +++ b/app/src/main/res/drawable/divider_item_line_space_16.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/community_answer_item.xml b/app/src/main/res/layout/community_answer_item.xml index b549164a28..2edbf2bd19 100644 --- a/app/src/main/res/layout/community_answer_item.xml +++ b/app/src/main/res/layout/community_answer_item.xml @@ -194,6 +194,78 @@ app:layout_constraintTop_toBottomOf="@+id/content" app:offset="40dp" /> + + + + + + + + + + + - + @@ -15,67 +16,62 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/reuse_listview_item_style" - android:paddingLeft="20dp" - android:paddingBottom="14dp"> + android:paddingLeft="16dp" + android:paddingTop="20dp" + android:paddingRight="16dp" + android:paddingBottom="16dp"> - - - + android:textSize="11sp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/article_draft_title" + tools:text="地下城与勇士" /> - - + app:layout_constraintTop_toBottomOf="@+id/article_draft_title" /> \ No newline at end of file diff --git a/app/src/main/res/layout/community_question_draft_item.xml b/app/src/main/res/layout/community_question_draft_item.xml new file mode 100644 index 0000000000..8b46456535 --- /dev/null +++ b/app/src/main/res/layout/community_question_draft_item.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_new_alert.xml b/app/src/main/res/layout/dialog_new_alert.xml index 1010100ca1..2246d2b545 100644 --- a/app/src/main/res/layout/dialog_new_alert.xml +++ b/app/src/main/res/layout/dialog_new_alert.xml @@ -40,7 +40,7 @@ app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/title" - tools:text="内容内容内容内容内容内容"/> + tools:text="内容内容内容内容内容内容" /> + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_answer_post.xml b/app/src/main/res/menu/menu_answer_post.xml index c4792fb7e9..bf5a8a317b 100644 --- a/app/src/main/res/menu/menu_answer_post.xml +++ b/app/src/main/res/menu/menu_answer_post.xml @@ -3,11 +3,10 @@ xmlns:app = "http://schemas.android.com/apk/res-auto" > + android:title="草稿" + app:showAsAction="always" /> - +