rss收藏增加分组管理 (#4236)

* rss收藏增加分组和标题编辑选项

* 优化变量命名

* 订阅收藏按分组展示

* 优化:订阅收藏分组展示

* 优化

* 优化rss收藏分组管理

* 优化数据库版本控制
This commit is contained in:
老牛
2024-09-26 14:50:17 +08:00
committed by GitHub
parent 72af4d737a
commit a762ba4587
14 changed files with 2328 additions and 55 deletions
File diff suppressed because it is too large Load Diff
@@ -63,7 +63,7 @@ val appDb by lazy {
} }
@Database( @Database(
version = 71, version = 72,
exportSchema = true, exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
@@ -99,6 +99,7 @@ val appDb by lazy {
AutoMigration(from = 68, to = 69), AutoMigration(from = 68, to = 69),
AutoMigration(from = 69, to = 70), AutoMigration(from = 69, to = 70),
AutoMigration(from = 70, to = 71), AutoMigration(from = 70, to = 71),
AutoMigration(from = 71, to = 72),
] ]
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@@ -13,7 +13,7 @@ interface RssArticleDao {
@Query( @Query(
"""select t1.link, t1.sort, t1.origin, t1.`order`, t1.title, t1.content, """select t1.link, t1.sort, t1.origin, t1.`order`, t1.title, t1.content,
t1.description, t1.image, t1.pubDate, t1.variable, ifNull(t2.read, 0) as read t1.description, t1.image, t1.`group`, t1.pubDate, t1.variable, ifNull(t2.read, 0) as read
from rssArticles as t1 left join rssReadRecords as t2 from rssArticles as t1 left join rssReadRecords as t2
on t1.link = t2.record where origin = :origin and sort = :sort on t1.link = t2.record where origin = :origin and sort = :sort
order by `order` desc""" order by `order` desc"""
@@ -10,6 +10,12 @@ interface RssStarDao {
@get:Query("select * from rssStars order by starTime desc") @get:Query("select * from rssStars order by starTime desc")
val all: List<RssStar> val all: List<RssStar>
@Query("select `group` from rssStars group by `group` order by `group`")
fun groupList(): Flow<List<String>>
@Query("select * from rssStars where `group` = :group order by starTime desc")
fun getByGroup(group: String): Flow<List<RssStar>>
@Query("select * from rssStars where origin = :origin and link = :link") @Query("select * from rssStars where origin = :origin and link = :link")
fun get(origin: String, link: String): RssStar? fun get(origin: String, link: String): RssStar?
@@ -1,12 +1,12 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore import androidx.room.Ignore
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
@Entity( @Entity(
tableName = "rssArticles", tableName = "rssArticles",
primaryKeys = ["origin", "link"] primaryKeys = ["origin", "link"]
@@ -21,6 +21,8 @@ data class RssArticle(
var description: String? = null, var description: String? = null,
var content: String? = null, var content: String? = null,
var image: String? = null, var image: String? = null,
@ColumnInfo(defaultValue = "默认分组")
var group: String = "默认分组",
var read: Boolean = false, var read: Boolean = false,
override var variable: String? = null override var variable: String? = null
) : BaseRssArticle { ) : BaseRssArticle {
@@ -49,6 +51,7 @@ data class RssArticle(
description = description, description = description,
content = content, content = content,
image = image, image = image,
group = group,
variable = variable variable = variable
) )
} }
@@ -1,5 +1,6 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore import androidx.room.Ignore
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
@@ -21,6 +22,8 @@ data class RssStar(
var description: String? = null, var description: String? = null,
var content: String? = null, var content: String? = null,
var image: String? = null, var image: String? = null,
@ColumnInfo(defaultValue = "默认分组")
var group: String = "默认分组",
override var variable: String? = null override var variable: String? = null
) : BaseRssArticle { ) : BaseRssArticle {
@@ -40,6 +43,7 @@ data class RssStar(
description = description, description = description,
content = content, content = content,
image = image, image = image,
group = group,
variable = variable variable = variable
) )
} }
@@ -1,62 +1,84 @@
@file:Suppress("DEPRECATION")
package io.legado.app.ui.rss.favorites package io.legado.app.ui.rss.favorites
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.RssStar
import io.legado.app.databinding.ActivityRssFavoritesBinding import io.legado.app.databinding.ActivityRssFavoritesBinding
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.rss.read.ReadRssActivity import io.legado.app.utils.gone
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
* 收藏夹 * 收藏夹
*/ */
class RssFavoritesActivity : BaseActivity<ActivityRssFavoritesBinding>(), class RssFavoritesActivity : BaseActivity<ActivityRssFavoritesBinding>(){
RssFavoritesAdapter.CallBack {
override val binding by viewBinding(ActivityRssFavoritesBinding::inflate) override val binding by viewBinding(ActivityRssFavoritesBinding::inflate)
private val adapter by lazy { RssFavoritesAdapter(this, this) } private val adapter by lazy { TabFragmentPageAdapter() }
private var groupList = mutableListOf<String>()
private var rssStarFlowJob: Job? = null
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
initView() binding.viewPager.adapter = adapter
initData() binding.tabLayout.setupWithViewPager(binding.viewPager)
binding.tabLayout.setSelectedTabIndicatorColor(accentColor)
upFragments()
} }
private fun initView() { private fun upFragments() {
binding.refreshLayout.setColorSchemeColors(accentColor) rssStarFlowJob?.cancel()
binding.recyclerView.let { rssStarFlowJob = lifecycleScope.launch {
it.layoutManager = LinearLayoutManager(this) appDb.rssStarDao.groupList().catch {
it.addItemDecoration(VerticalDivider(this)) AppLog.put("订阅分组数据获取失败\n${it.localizedMessage}", it)
it.adapter = adapter }.flowOn(IO).collect {
groupList.clear()
groupList.addAll(it)
if (groupList.size == 1) {
binding.tabLayout.gone()
} else {
binding.tabLayout.visible()
} }
} adapter.notifyDataSetChanged()
private fun initData() {
lifecycleScope.launch {
appDb.rssStarDao.liveAll().catch {
AppLog.put("订阅收藏夹界面获取数据失败\n${it.localizedMessage}", it)
}.flowOn(IO).conflate().collect {
adapter.setItems(it)
} }
} }
} }
override fun readRss(rssStar: RssStar) { private inner class TabFragmentPageAdapter :
startActivity<ReadRssActivity> { FragmentStatePagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
putExtra("title", rssStar.title)
putExtra("origin", rssStar.origin) override fun getItemPosition(`object`: Any): Int {
putExtra("link", rssStar.link) return POSITION_NONE
}
override fun getPageTitle(position: Int): CharSequence {
return groupList[position]
}
override fun getItem(position: Int): Fragment {
val group = groupList[position]
return RssFavoritesFragment(group)
}
override fun getCount(): Int {
return groupList.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
return fragment
} }
} }
} }
@@ -0,0 +1,81 @@
package io.legado.app.ui.rss.favorites
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.data.entities.RssArticle
import io.legado.app.databinding.DialogRssfavoritesBinding
import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.setLayout
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.launch
class RssFavoritesDialog() : BaseDialogFragment(R.layout.dialog_rssfavorites, true) {
constructor(rssArticle: RssArticle) : this() {
arguments = Bundle().apply {
putString("title", rssArticle.title)
putString("group", rssArticle.group)
}
}
private val binding by viewBinding(DialogRssfavoritesBinding::bind)
override fun onStart() {
super.onStart()
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor)
val arguments = arguments ?: let {
dismiss()
return
}
var title = arguments.getString("title") ?: "默认名称"
var group = arguments.getString("group") ?: "默认分组"
binding.run {
editTitle.setText(title)
editGroup.setText(group)
tvCancel.setOnClickListener {
dismiss()
}
tvOk.setOnClickListener {
val editTitle = editTitle.text.toString()
if(!TextUtils.isEmpty(editTitle)){
title = editTitle
}
val editGroup = editGroup.text.toString()
if(!TextUtils.isEmpty(editGroup)){
group = editGroup
}
lifecycleScope.launch {
callback?.updateFavorite(title, group)
dismiss()
}
}
tvFooterLeft.setOnClickListener {
lifecycleScope.launch {
callback?.deleteFavorite()
dismiss()
}
}
}
}
val callback get() = (parentFragment as? Callback) ?: (activity as? Callback)
interface Callback {
fun updateFavorite(title: String, group: String)
fun deleteFavorite()
}
}
@@ -0,0 +1,77 @@
package io.legado.app.ui.rss.favorites
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R
import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppLog
import io.legado.app.data.appDb
import io.legado.app.data.entities.RssStar
import io.legado.app.databinding.FragmentRssArticlesBinding
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.rss.read.ReadRssActivity
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
class RssFavoritesFragment() : VMBaseFragment<RssFavoritesViewModel>(R.layout.fragment_rss_articles),
RssFavoritesAdapter.CallBack {
constructor(group: String) : this() {
arguments = Bundle().apply {
putString("group", group)
}
}
private val binding by viewBinding(FragmentRssArticlesBinding::bind)
override val viewModel by viewModels<RssFavoritesViewModel>()
private val adapter: RssFavoritesAdapter by lazy {
RssFavoritesAdapter(requireContext(), this@RssFavoritesFragment)
}
private var articlesFlowJob: Job? = null
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
initView()
}
private fun initView() = binding.run {
refreshLayout.setEnabled(false)
recyclerView.setEdgeEffectColor(primaryColor)
recyclerView.layoutManager = run {
recyclerView.addItemDecoration(VerticalDivider(requireContext()))
LinearLayoutManager(requireContext())
}
recyclerView.adapter = adapter
loadArticles()
}
private fun loadArticles() {
articlesFlowJob?.cancel()
articlesFlowJob = lifecycleScope.launch {
val group = arguments?.getString("group") ?: "默认分组"
appDb.rssStarDao.getByGroup(group).catch {
AppLog.put("订阅文章界面获取数据失败\n${it.localizedMessage}", it)
}.flowOn(IO).collect {
adapter.setItems(it)
}
}
}
override fun readRss(rssStar: RssStar) {
startActivity<ReadRssActivity> {
putExtra("title", rssStar.title)
putExtra("origin", rssStar.origin)
putExtra("link", rssStar.link)
}
}
}
@@ -0,0 +1,9 @@
package io.legado.app.ui.rss.favorites
import android.app.Application
import io.legado.app.base.BaseViewModel
class RssFavoritesViewModel(application: Application) : BaseViewModel(application) {
}
@@ -42,6 +42,7 @@ import io.legado.app.model.Download
import io.legado.app.ui.association.OnLineImportActivity import io.legado.app.ui.association.OnLineImportActivity
import io.legado.app.ui.file.HandleFileContract import io.legado.app.ui.file.HandleFileContract
import io.legado.app.ui.login.SourceLoginActivity import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.rss.favorites.RssFavoritesDialog
import io.legado.app.utils.ACache import io.legado.app.utils.ACache
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.get import io.legado.app.utils.get
@@ -53,6 +54,7 @@ import io.legado.app.utils.openUrl
import io.legado.app.utils.setDarkeningAllowed import io.legado.app.utils.setDarkeningAllowed
import io.legado.app.utils.setTintMutate import io.legado.app.utils.setTintMutate
import io.legado.app.utils.share import io.legado.app.utils.share
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.textArray import io.legado.app.utils.textArray
@@ -70,7 +72,7 @@ import java.util.regex.PatternSyntaxException
/** /**
* rss阅读界面 * rss阅读界面
*/ */
class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>() { class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>(), RssFavoritesDialog.Callback {
override val binding by viewBinding(ActivityRssReadBinding::inflate) override val binding by viewBinding(ActivityRssReadBinding::inflate)
override val viewModel by viewModels<ReadRssViewModel>() override val viewModel by viewModels<ReadRssViewModel>()
@@ -151,8 +153,12 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
R.id.menu_rss_refresh -> viewModel.refresh { R.id.menu_rss_refresh -> viewModel.refresh {
binding.webView.reload() binding.webView.reload()
} }
R.id.menu_rss_star -> {
R.id.menu_rss_star -> viewModel.favorite() viewModel.addFavorite()
viewModel.rssArticle?.let {
showDialogFragment(RssFavoritesDialog(it))
}
}
R.id.menu_share_it -> { R.id.menu_share_it -> {
binding.webView.url?.let { binding.webView.url?.let {
share(it) share(it)
@@ -174,6 +180,16 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
override fun updateFavorite(title: String, group: String) {
viewModel.rssArticle?.title = title
viewModel.rssArticle?.group = group
viewModel.updateFavorite()
}
override fun deleteFavorite() {
viewModel.delFavorite()
}
@JavascriptInterface @JavascriptInterface
fun isNightTheme(): Boolean { fun isNightTheme(): Boolean {
return AppConfig.isNightTheme return AppConfig.isNightTheme
@@ -188,7 +204,7 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
} }
} }
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled", "JavascriptInterface")
private fun initWebView() { private fun initWebView() {
binding.progressBar.fontColor = accentColor binding.progressBar.fontColor = accentColor
binding.webView.webChromeClient = CustomWebChromeClient() binding.webView.webChromeClient = CustomWebChromeClient()
@@ -135,6 +135,39 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), J
} }
} }
fun addFavorite() {
execute {
rssStar ?: rssArticle?.toStar()?.let {
appDb.rssStarDao.insert(it)
rssStar = it
}
}.onSuccess {
upStarMenuData.postValue(true)
}
}
fun updateFavorite() {
execute {
rssArticle?.toStar()?.let {
appDb.rssStarDao.update(it)
rssStar = it
}
}.onSuccess {
upStarMenuData.postValue(true)
}
}
fun delFavorite() {
execute {
rssStar?.let {
appDb.rssStarDao.delete(it.origin, it.link)
rssStar = null
}
}.onSuccess {
upStarMenuData.postValue(true)
}
}
fun saveImage(webPic: String?, uri: Uri) { fun saveImage(webPic: String?, uri: Uri) {
webPic ?: return webPic ?: return
execute { execute {
@@ -1,30 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:orientation="vertical">
<io.legado.app.ui.widget.TitleBar <io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:title="@string/favorites" app:title="@string/favorites" />
app:layout_constraintTop_toTopOf="parent" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/refresh_layout" android:id="@+id/tab_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:tabMode="scrollable" />
app:layout_constraintTop_toBottomOf="@id/title_bar"
app:layout_constraintVertical_bias="0.0">
<androidx.recyclerview.widget.RecyclerView <androidx.viewpager.widget.ViewPager
android:id="@+id/recycler_view" android:id="@+id/view_pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="wrap_content"
tools:ignore="SpeakableTextPresentCheck" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:id="@+id/vw_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/shape_card_view"
android:orientation="vertical"
tools:ignore="UselessParent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarStyle"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title="@string/favorite"
app:titleTextAppearance="@style/ToolbarTitle" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="ifContentScrolls">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<io.legado.app.ui.widget.text.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="3dp">
<io.legado.app.lib.theme.view.ThemeEditText
android:id="@+id/edit_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title"
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</io.legado.app.ui.widget.text.TextInputLayout>
<io.legado.app.ui.widget.text.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="3dp">
<io.legado.app.lib.theme.view.ThemeEditText
android:id="@+id/edit_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/group_name"
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</io.legado.app.ui.widget.text.TextInputLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="12dp"
android:paddingRight="12dp"
app:flexWrap="wrap"
app:justifyContent="space_between">
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/tv_footer_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/delete"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/tv_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/cancel"
tools:ignore="RtlHardcoded" />
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/tv_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/ok"
tools:ignore="RtlHardcoded" />
</LinearLayout>
</com.google.android.flexbox.FlexboxLayout>
</LinearLayout>
</FrameLayout>