pull/32/head
Administrator 5 years ago
parent 6d98e6748c
commit 878f512c51
  1. 8
      app/src/main/java/io/legado/app/base/BaseViewModel.kt
  2. 26
      app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt
  3. 107
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  4. 5
      app/src/main/java/io/legado/app/model/WebBook.kt
  5. 1
      app/src/main/java/io/legado/app/model/webbook/SourceDebug.kt
  6. 7
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  7. 23
      app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt
  8. 1
      app/src/main/java/io/legado/app/ui/sourcedebug/SourceDebugModel.kt
  9. 77
      app/src/main/java/io/legado/app/ui/widget/PageView.kt
  10. 12
      app/src/main/java/io/legado/app/ui/widget/page/DataSource.kt
  11. 39
      app/src/main/java/io/legado/app/ui/widget/page/PageAnimDelegate.kt
  12. 11
      app/src/main/java/io/legado/app/ui/widget/page/PageFactory.kt
  13. 55
      app/src/main/java/io/legado/app/ui/widget/page/PageView.kt
  14. 3
      app/src/main/java/io/legado/app/ui/widget/page/TextChapter.kt
  15. 3
      app/src/main/java/io/legado/app/ui/widget/page/TextPage.kt
  16. 26
      app/src/main/java/io/legado/app/ui/widget/page/TextPageFactory.kt
  17. 2
      app/src/main/res/layout/activity_read.xml
  18. 42
      app/src/main/res/layout/page_view.xml

@ -12,12 +12,12 @@ import org.jetbrains.anko.AnkoLogger
open class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope by MainScope(), open class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope by MainScope(),
AnkoLogger { AnkoLogger {
fun <T> execute(block: suspend CoroutineScope.() -> T): Coroutine<T> { fun <T> execute(scope: CoroutineScope = this, block: suspend CoroutineScope.() -> T): Coroutine<T> {
return Coroutine.async(this) { block() } return Coroutine.async(scope) { block() }
} }
fun <T> submit(block: suspend CoroutineScope.() -> Deferred<T>): Coroutine<T> { fun <R> submit(scope: CoroutineScope = this, block: suspend CoroutineScope.() -> Deferred<R>): Coroutine<R> {
return Coroutine.async(this) { block().await() } return Coroutine.async(scope) { block().await() }
} }
fun <T> plus(coroutine: Coroutine<T>): Coroutine<T> { fun <T> plus(coroutine: Coroutine<T>): Coroutine<T> {

@ -7,7 +7,7 @@ class Coroutine<T>() {
companion object { companion object {
private val DEFAULT = MainScope() val DEFAULT = MainScope()
fun <T> async(scope: CoroutineScope = DEFAULT, block: suspend CoroutineScope.() -> T): Coroutine<T> { fun <T> async(scope: CoroutineScope = DEFAULT, block: suspend CoroutineScope.() -> T): Coroutine<T> {
return Coroutine(scope, block) return Coroutine(scope, block)
@ -22,6 +22,7 @@ class Coroutine<T>() {
private var job: Job? = null private var job: Job? = null
private var start: (suspend CoroutineScope.() -> Unit)? = null private var start: (suspend CoroutineScope.() -> Unit)? = null
private var execute: (suspend CoroutineScope.(T?) -> Unit)? = null
private var success: (suspend CoroutineScope.(T?) -> Unit)? = null private var success: (suspend CoroutineScope.(T?) -> Unit)? = null
private var error: (suspend CoroutineScope.(Throwable) -> Unit)? = null private var error: (suspend CoroutineScope.(Throwable) -> Unit)? = null
private var finally: (suspend CoroutineScope.() -> Unit)? = null private var finally: (suspend CoroutineScope.() -> Unit)? = null
@ -80,6 +81,15 @@ class Coroutine<T>() {
return this@Coroutine return this@Coroutine
} }
fun onExecute(execute: suspend CoroutineScope.(T?) -> Unit): Coroutine<T> {
if (this.interceptor != null) {
this.interceptor!!.execute = execute
} else {
this.execute = execute
}
return this@Coroutine
}
fun onSuccess(success: suspend CoroutineScope.(T?) -> Unit): Coroutine<T> { fun onSuccess(success: suspend CoroutineScope.(T?) -> Unit): Coroutine<T> {
if (this.interceptor != null) { if (this.interceptor != null) {
this.interceptor!!.success = success this.interceptor!!.success = success
@ -117,7 +127,7 @@ class Coroutine<T>() {
{ {
start?.invoke(this) start?.invoke(this)
val result = executeBlock(block, timeMillis ?: 0L) val result = executeBlockIO(block, timeMillis ?: 0L)
success?.invoke(this, result) success?.invoke(this, result)
}, },
@ -136,11 +146,15 @@ class Coroutine<T>() {
}) })
} }
private suspend fun executeBlock(block: suspend CoroutineScope.() -> T, timeMillis: Long): T? { private suspend fun executeBlockIO(block: suspend CoroutineScope.() -> T, timeMillis: Long): T? {
val asyncBlock = withContext(Dispatchers.IO) { val result: T = withContext(Dispatchers.IO) {
block() val b = block()
execute?.invoke(this, b)
b
} }
return if (timeMillis > 0L) withTimeout(timeMillis) { asyncBlock } else asyncBlock return if (timeMillis > 0L) withTimeout(timeMillis) { result } else result
} }
private suspend fun tryCatch( private suspend fun tryCatch(

@ -102,58 +102,61 @@ object Restore {
source.bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: "" source.bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: ""
if (source.bookSourceUrl.isBlank()) continue if (source.bookSourceUrl.isBlank()) continue
if (source.bookSourceUrl in existingSources) continue if (source.bookSourceUrl in existingSources) continue
source.bookSourceName = jsonItem.readString("bookSourceName") ?: "" runCatching {
source.bookSourceGroup = jsonItem.readString("bookSourceGroup") ?: "" source.bookSourceName = jsonItem.readString("bookSourceName") ?: ""
source.loginUrl = jsonItem.readString("loginUrl") source.bookSourceGroup = jsonItem.readString("bookSourceGroup") ?: ""
source.bookUrlPattern = jsonItem.readString("ruleBookUrlPattern") source.loginUrl = jsonItem.readString("loginUrl")
source.customOrder = jsonItem.readInt("serialNumber") ?: 0 source.bookUrlPattern = jsonItem.readString("ruleBookUrlPattern")
val searchRule = SearchRule( source.customOrder = jsonItem.readInt("serialNumber") ?: 0
searchUrl = OldRule.toNewUrl(jsonItem.readString("ruleSearchUrl")), val searchRule = SearchRule(
bookList = jsonItem.readString("ruleSearchList"), searchUrl = OldRule.toNewUrl(jsonItem.readString("ruleSearchUrl")),
name = jsonItem.readString("ruleSearchName"), bookList = jsonItem.readString("ruleSearchList"),
author = jsonItem.readString("ruleSearchAuthor"), name = jsonItem.readString("ruleSearchName"),
intro = jsonItem.readString("ruleSearchIntroduce"), author = jsonItem.readString("ruleSearchAuthor"),
kind = jsonItem.readString("ruleSearchKind"), intro = jsonItem.readString("ruleSearchIntroduce"),
bookUrl = jsonItem.readString("ruleSearchNoteUrl"), kind = jsonItem.readString("ruleSearchKind"),
coverUrl = jsonItem.readString("ruleSearchCoverUrl"), bookUrl = jsonItem.readString("ruleSearchNoteUrl"),
lastChapter = jsonItem.readString("ruleSearchLastChapter") coverUrl = jsonItem.readString("ruleSearchCoverUrl"),
) lastChapter = jsonItem.readString("ruleSearchLastChapter")
source.ruleSearch = GSON.toJson(searchRule) )
val exploreRule = ExploreRule( source.ruleSearch = GSON.toJson(searchRule)
exploreUrl = OldRule.toNewUrl(jsonItem.readString("ruleFindUrl")), val exploreRule = ExploreRule(
bookList = jsonItem.readString("ruleFindList"), exploreUrl = OldRule.toNewUrl(jsonItem.readString("ruleFindUrl")),
name = jsonItem.readString("ruleFindName"), bookList = jsonItem.readString("ruleFindList"),
author = jsonItem.readString("ruleFindAuthor"), name = jsonItem.readString("ruleFindName"),
intro = jsonItem.readString("ruleFindIntroduce"), author = jsonItem.readString("ruleFindAuthor"),
kind = jsonItem.readString("ruleFindKind"), intro = jsonItem.readString("ruleFindIntroduce"),
bookUrl = jsonItem.readString("ruleFindNoteUrl"), kind = jsonItem.readString("ruleFindKind"),
coverUrl = jsonItem.readString("ruleFindCoverUrl"), bookUrl = jsonItem.readString("ruleFindNoteUrl"),
lastChapter = jsonItem.readString("ruleFindLastChapter") coverUrl = jsonItem.readString("ruleFindCoverUrl"),
) lastChapter = jsonItem.readString("ruleFindLastChapter")
source.ruleExplore = GSON.toJson(exploreRule) )
val bookInfoRule = BookInfoRule( source.ruleExplore = GSON.toJson(exploreRule)
init = jsonItem.readString("ruleBookInfoInit"), val bookInfoRule = BookInfoRule(
name = jsonItem.readString("ruleBookName"), init = jsonItem.readString("ruleBookInfoInit"),
author = jsonItem.readString("ruleBookAuthor"), name = jsonItem.readString("ruleBookName"),
intro = jsonItem.readString("ruleIntroduce"), author = jsonItem.readString("ruleBookAuthor"),
kind = jsonItem.readString("ruleBookKind"), intro = jsonItem.readString("ruleIntroduce"),
coverUrl = jsonItem.readString("ruleCoverUrl"), kind = jsonItem.readString("ruleBookKind"),
lastChapter = jsonItem.readString("ruleBookLastChapter"), coverUrl = jsonItem.readString("ruleCoverUrl"),
tocUrl = jsonItem.readString("ruleChapterUrl") lastChapter = jsonItem.readString("ruleBookLastChapter"),
) tocUrl = jsonItem.readString("ruleChapterUrl")
source.ruleBookInfo = GSON.toJson(bookInfoRule) )
val chapterRule = TocRule( source.ruleBookInfo = GSON.toJson(bookInfoRule)
chapterList = jsonItem.readString("ruleChapterList"), val chapterRule = TocRule(
chapterName = jsonItem.readString("ruleChapterName"), chapterList = jsonItem.readString("ruleChapterList"),
chapterUrl = jsonItem.readString("ruleContentUrl"), chapterName = jsonItem.readString("ruleChapterName"),
nextTocUrl = jsonItem.readString("ruleChapterUrlNext") chapterUrl = jsonItem.readString("ruleContentUrl"),
) nextTocUrl = jsonItem.readString("ruleChapterUrlNext")
source.ruleToc = GSON.toJson(chapterRule) )
val contentRule = ContentRule( source.ruleToc = GSON.toJson(chapterRule)
content = jsonItem.readString("ruleBookContent"), val contentRule = ContentRule(
nextContentUrl = jsonItem.readString("ruleContentUrlNext") content = jsonItem.readString("ruleBookContent"),
) nextContentUrl = jsonItem.readString("ruleContentUrlNext")
source.ruleContent = GSON.toJson(contentRule) )
source.ruleContent = GSON.toJson(contentRule)
}
bookSources.add(source) bookSources.add(source)
} }
App.db.bookSourceDao().insert(*bookSources.toTypedArray()) App.db.bookSourceDao().insert(*bookSources.toTypedArray())

@ -10,14 +10,15 @@ import io.legado.app.model.webbook.BookChapterList
import io.legado.app.model.webbook.BookContent import io.legado.app.model.webbook.BookContent
import io.legado.app.model.webbook.BookInfo import io.legado.app.model.webbook.BookInfo
import io.legado.app.model.webbook.BookList import io.legado.app.model.webbook.BookList
import kotlinx.coroutines.CoroutineScope
class WebBook(private val bookSource: BookSource) { class WebBook(private val bookSource: BookSource) {
val sourceUrl: String val sourceUrl: String
get() = bookSource.bookSourceUrl get() = bookSource.bookSourceUrl
fun searchBook(key: String, page: Int?, isSearch: Boolean = true): Coroutine<List<SearchBook>> { fun searchBook(key: String, page: Int?, isSearch: Boolean = true, scope: CoroutineScope = Coroutine.DEFAULT): Coroutine<List<SearchBook>> {
return Coroutine.async { return Coroutine.async(scope) {
bookSource.getSearchRule().searchUrl?.let { searchUrl -> bookSource.getSearchRule().searchUrl?.let { searchUrl ->
val analyzeUrl = AnalyzeUrl(searchUrl, key, page, baseUrl = sourceUrl) val analyzeUrl = AnalyzeUrl(searchUrl, key, page, baseUrl = sourceUrl)
val response = analyzeUrl.getResponseAsync().await() val response = analyzeUrl.getResponseAsync().await()

@ -5,6 +5,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import io.legado.app.utils.htmlFormat import io.legado.app.utils.htmlFormat
import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isAbsUrl

@ -14,13 +14,14 @@ import io.legado.app.constant.Bus
import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.Selector
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.main.bookshelf.BookshelfFragment import io.legado.app.ui.main.bookshelf.BookshelfFragment
import io.legado.app.ui.main.findbook.FindBookFragment import io.legado.app.ui.main.findbook.FindBookFragment
import io.legado.app.ui.main.my.MyFragment import io.legado.app.ui.main.my.MyFragment
import io.legado.app.ui.main.rss.RssFragment import io.legado.app.ui.main.rss.RssFragment
import io.legado.app.utils.* import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getViewModel
import io.legado.app.utils.observeEvent
import io.legado.app.utils.putPrefInt
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

@ -4,37 +4,32 @@ import android.app.Application
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
class SearchViewModel(application: Application) : BaseViewModel(application) { class SearchViewModel(application: Application) : BaseViewModel(application) {
private val tasks: CompositeCoroutine = CompositeCoroutine() private var task: Coroutine<*>? = null
var searchPage = 0 var searchPage = 0
fun search(key: String, start: ((startTime: Long) -> Unit)? = null, finally: (() -> Unit)? = null) { fun search(key: String, start: ((startTime: Long) -> Unit)? = null, finally: (() -> Unit)? = null) {
if (key.isEmpty()) return if (key.isEmpty()) return
tasks.clear() task?.cancel()
start?.invoke(System.currentTimeMillis()) start?.invoke(System.currentTimeMillis())
execute { task = execute {//onCleared时自动取消
val bookSourceList = App.db.bookSourceDao().allEnabled val bookSourceList = App.db.bookSourceDao().allEnabled
for (item in bookSourceList) { for (item in bookSourceList) {
val search = WebBook(item).searchBook(key, searchPage) //task取消时自动取消 by (scope = this@execute)
.onSuccess { searchBookS -> WebBook(item).searchBook(key, searchPage, scope = this)
.onExecute { searchBookS ->
searchBookS?.let { searchBookS?.let {
execute { App.db.searchBookDao().insert(*it.toTypedArray())
App.db.searchBookDao().insert(*it.toTypedArray())
}
} }
} }
tasks.add(search)
} }
}.onError { }.onError {
it.printStackTrace() it.printStackTrace()
} }
} }
override fun onCleared() {
super.onCleared()
tasks.clear()
}
} }

@ -44,4 +44,5 @@ class SourceDebugModel(application: Application) : BaseViewModel(application), S
super.onCleared() super.onCleared()
SourceDebug.cancelDebug(true) SourceDebug.cancelDebug(true)
} }
} }

@ -1,77 +0,0 @@
package io.legado.app.ui.widget
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.utils.screenshot
import kotlin.math.abs
class PageView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {
private var bitmap: Bitmap? = null
private var downX: Float = 0.toFloat()
private var offset: Float = 0.toFloat()
private val srcRect: Rect = Rect()
private val destRect: Rect = Rect()
private val shadowDrawable: GradientDrawable
init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
shadowDrawable = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT, shadowColors
)
shadowDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT
}
override fun onDraw(canvas: Canvas?) {
canvas?.save()
super.onDraw(canvas)
canvas?.restore()
bitmap?.let {
srcRect.set(0, 0, width, height)
destRect.set(-width + offset.toInt(), 0, offset.toInt(), height)
canvas?.drawBitmap(it, srcRect, destRect, null)
addShadow(offset.toInt(), canvas)
}
}
//添加阴影
private fun addShadow(left: Int, canvas: Canvas?) {
canvas?.let {
shadowDrawable.setBounds(left, 0, left + 30, height)
shadowDrawable.draw(it)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
bitmap = screenshot()
Log.e("TAG", "bitmap == null: " + (bitmap == null))
downX = event.x
offset = 0.toFloat()
invalidate()
}
MotionEvent.ACTION_MOVE -> {
offset = abs(event.x - downX)
invalidate()
}
MotionEvent.ACTION_UP -> {
bitmap = null
invalidate()
}
}
return true
}
}

@ -0,0 +1,12 @@
package io.legado.app.ui.widget.page
interface DataSource {
fun getChapterPosition()
fun getChapter(position: Int): TextChapter
fun getNextChapter(): TextChapter
fun getPreviousChapter(): TextChapter
}

@ -0,0 +1,39 @@
package io.legado.app.ui.widget.page
import android.widget.Scroller
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
abstract class PageAnimDelegate(protected val pageView: PageView) {
protected val scroller: Scroller = Scroller(pageView.context, FastOutLinearInInterpolator())
//起始点
protected var startX: Float = 0.toFloat()
protected var startY: Float = 0.toFloat()
//触碰点
protected var touchX: Float = 0.toFloat()
protected var touchY: Float = 0.toFloat()
//上一个触碰点
protected var lastX: Float = 0.toFloat()
protected var lastY: Float = 0.toFloat()
protected var isRunning = false
protected var isStarted = false
fun setStartPoint(x: Float, y: Float) {
startX = x
startY = y
lastX = startX
lastY = startY
}
fun setTouchPoint(x: Float, y: Float) {
lastX = touchX
lastY = touchY
touchX = x
touchY = y
}
}

@ -0,0 +1,11 @@
package io.legado.app.ui.widget.page
abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun pageAt(index: Int): DATA
abstract fun nextPage(): DATA
abstract fun previousPage(): DATA
}

@ -0,0 +1,55 @@
package io.legado.app.ui.widget.page
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.R
import io.legado.app.utils.screenshot
import kotlin.math.abs
class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {
private var bitmap: Bitmap? = null
private var downX: Float = 0.toFloat()
private var offset: Float = 0.toFloat()
private val srcRect: Rect = Rect()
private val destRect: Rect = Rect()
private val shadowDrawable: GradientDrawable
init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
shadowDrawable = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT, shadowColors
)
shadowDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT
inflate(context, R.layout.page_view, this)
}
override fun onDraw(canvas: Canvas?) {
canvas?.save()
super.onDraw(canvas)
canvas?.restore()
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return true
}
fun setPageFactory(factory: PageFactory<*>){
}
}

@ -0,0 +1,3 @@
package io.legado.app.ui.widget.page
data class TextChapter(val position: Int, val pages: List<TextPage>)

@ -0,0 +1,3 @@
package io.legado.app.ui.widget.page
data class TextPage(val index: Int, val text: String)

@ -0,0 +1,26 @@
package io.legado.app.ui.widget.page
class TextPageFactory private constructor(dataSource: DataSource) : PageFactory<TextPage>(dataSource) {
companion object{
fun create(dataSource: DataSource): TextPageFactory{
return TextPageFactory(dataSource)
}
}
private var index: Int = 0
override fun pageAt(index: Int): TextPage {
TODO("todo...")
}
override fun nextPage(): TextPage {
return TextPage(index.plus(1), "index:$index")
}
override fun previousPage(): TextPage {
return TextPage(index.minus(1), "index:$index")
}
}

@ -4,7 +4,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<io.legado.app.ui.widget.PageView <io.legado.app.ui.widget.page.PageView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="30dp" android:padding="30dp"

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/page_panel"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:showDividers="middle"
app:divider="@drawable/ic_divider">
<View
android:id="@+id/top_status_bar"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</FrameLayout>
<View
android:id="@+id/bottom_status_bar"
android:layout_width="match_parent"
android:layout_height="25dp"/>
</androidx.appcompat.widget.LinearLayoutCompat>
Loading…
Cancel
Save