refactor:Use viewModel
This commit is contained in:
@@ -11,6 +11,7 @@ import com.google.android.material.color.DynamicColors
|
||||
import android.content.Intent
|
||||
import com.lsp.view.service.DownloadService
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.lsp.view.repository.PostRepository
|
||||
|
||||
class YandViewApplication : Application(), DefaultLifecycleObserver {
|
||||
private val connection: ServiceConnection = object : ServiceConnection {
|
||||
@@ -20,6 +21,7 @@ class YandViewApplication : Application(), DefaultLifecycleObserver {
|
||||
|
||||
override fun onServiceDisconnected(componentName: ComponentName) {}
|
||||
}
|
||||
val repository = PostRepository()
|
||||
|
||||
override fun onCreate() {
|
||||
super<Application>.onCreate()
|
||||
|
||||
@@ -15,7 +15,7 @@ import com.lsp.view.YandViewApplication
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.activity.BaseActivity
|
||||
import com.lsp.view.activity.main.MainActivity
|
||||
import com.lsp.view.bean.Tags
|
||||
import com.lsp.view.repository.bean.Tags
|
||||
|
||||
class FavTagActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.bean.Tags
|
||||
import com.lsp.view.repository.bean.Tags
|
||||
|
||||
class FavTagAdapter(val tagList: ArrayList<Tags>, val context: Context) :
|
||||
RecyclerView.Adapter<FavTagAdapter.ViewHolder>() {
|
||||
|
||||
@@ -4,11 +4,13 @@ import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.content.Intent
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
@@ -18,100 +20,64 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.lsp.view.YandViewApplication
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.activity.BaseActivity
|
||||
import com.lsp.view.activity.favtag.FavTagActivity
|
||||
import com.lsp.view.activity.model.MainActivityModelImpl
|
||||
import com.lsp.view.activity.setting.SettingsActivity
|
||||
import com.lsp.view.bean.YandPost
|
||||
import com.lsp.view.util.CallBackStatus
|
||||
import com.lsp.view.model.MainViewModel
|
||||
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
private lateinit var search: EditText
|
||||
private var shortAnnotationDuration: Int = 0
|
||||
private var nowPage = 1
|
||||
private val adapter: PostAdapter = PostAdapter(this, ArrayList())
|
||||
private var username: String? = ""
|
||||
private lateinit var sourceUrlArray: Array<String>
|
||||
private lateinit var sourceNameArray: Array<String>
|
||||
private var nowSourceName: String? = null
|
||||
val TAG = javaClass.simpleName
|
||||
private lateinit var layoutManager: RecyclerView.LayoutManager
|
||||
private var safeMode: Boolean = true //安全模式
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private var barShow = false
|
||||
private var tags: String? = ""
|
||||
private val ISREFRESH = 1
|
||||
private val ISADDDATA = 0
|
||||
private lateinit var toolbar:Toolbar
|
||||
private lateinit var viewModel: MainViewModel
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
viewModel = MainViewModel.provideFactory((application as YandViewApplication).repository, this,this).create(MainViewModel::class.java)
|
||||
|
||||
toolbar = findViewById(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
val appbar = findViewById<AppBarLayout>(R.id.appbar)
|
||||
val nowHeight = appbar.layoutParams.height
|
||||
appbar.layoutParams.height = (application as YandViewApplication).statusBarHeight()+nowHeight
|
||||
//刷新
|
||||
val refresh =
|
||||
findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(R.id.swipeRefreshLayout)
|
||||
|
||||
//到达底部,加载更多数据
|
||||
adapter.setLoadMoreListener(object : PostAdapter.OnLoadMoreListener {
|
||||
//重写接口
|
||||
override fun loadMore(position: Int) {
|
||||
loadData(tags,++nowPage,ISADDDATA)
|
||||
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
//初始化数据
|
||||
sourceNameArray = resources.getStringArray(R.array.pic_source)
|
||||
sourceUrlArray = resources.getStringArray(R.array.url_source)
|
||||
val configSp = getSharedPreferences("com.lsp.view_preferences", 0)
|
||||
if (configSp.getString("source_name", null) == null) {
|
||||
configSp.edit().putString("source_name", "yande.re").apply()
|
||||
configSp.edit().putString("type", "0").apply()
|
||||
viewModel.uiState.value.isRefreshing.observe(this) {
|
||||
refresh.isRefreshing = it
|
||||
}
|
||||
nowSourceName = configSp.getString("source_name","yande.re")
|
||||
safeMode = configSp.getBoolean("safe_mode",true)
|
||||
|
||||
refresh.setOnRefreshListener {
|
||||
viewModel.fetchPostByRefresh()
|
||||
}
|
||||
|
||||
viewModel.adapter.apply {
|
||||
setLoadMoreListener(object :PostAdapter.OnScrollToBottom{
|
||||
override fun event(position: Int) {
|
||||
viewModel.appendPost()
|
||||
}
|
||||
|
||||
//横屏逻辑
|
||||
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
|
||||
recyclerView = findViewById(R.id.recyclerview)
|
||||
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
|
||||
|
||||
//收藏Tag
|
||||
val swipeRefreshLayout =
|
||||
findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(
|
||||
R.id.swipeRefreshLayout
|
||||
)
|
||||
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
|
||||
})
|
||||
}
|
||||
|
||||
findViewById<RecyclerView>(R.id.recyclerview).apply {
|
||||
val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
|
||||
this.layoutManager = layoutManager
|
||||
this.adapter = viewModel.adapter
|
||||
}
|
||||
|
||||
search = findViewById(R.id.search)
|
||||
//快捷搜索tag 来自PicActivity
|
||||
val searchTag = intent.getStringExtra("searchTag")
|
||||
if (searchTag != null) {
|
||||
//tag按钮搜索
|
||||
this.search.setText(searchTag)
|
||||
action(searchTag)
|
||||
} else {
|
||||
//初次启动
|
||||
action("")
|
||||
}
|
||||
|
||||
|
||||
supportActionBar?.let {
|
||||
it.setDisplayHomeAsUpEnabled(true)
|
||||
@@ -122,17 +88,12 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
search.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
action(search.text.toString())
|
||||
viewModel.fetchNewPostBySearch(search.text.toString())
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
|
||||
//刷新
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
nowPage = 1
|
||||
loadData(tags, nowPage, ISREFRESH)
|
||||
}
|
||||
|
||||
//侧边栏
|
||||
val sp = getSharedPreferences("username", 0)
|
||||
@@ -144,10 +105,11 @@ class MainActivity : BaseActivity() {
|
||||
nav.setCheckedItem(R.id.photo)
|
||||
//设置侧边栏点击逻辑
|
||||
nav.setNavigationItemSelectedListener {
|
||||
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
|
||||
when (it.itemId) {
|
||||
//画廊
|
||||
R.id.photo -> {
|
||||
loadData( null, 1, ISREFRESH)
|
||||
viewModel.fetchPostByRefresh()
|
||||
drawerLayout.closeDrawers()
|
||||
true
|
||||
}
|
||||
@@ -174,15 +136,16 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
//加载源改变
|
||||
val configSp = getSharedPreferences("com.lsp.view_preferences", 0)
|
||||
if (nowSourceName != configSp.getString("source_name",null)){
|
||||
action("")
|
||||
viewModel.fetchPostByRefresh()
|
||||
nowSourceName = configSp.getString("source_name",null)
|
||||
}
|
||||
|
||||
if (safeMode != configSp.getBoolean("safe_mode",true)){
|
||||
action("")
|
||||
viewModel.fetchPostByRefresh()
|
||||
safeMode = configSp.getBoolean("safe_mode",true)
|
||||
}
|
||||
|
||||
@@ -202,8 +165,7 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
})
|
||||
hideIm()
|
||||
barShow = false
|
||||
|
||||
viewModel.uiState.value.switchShowSearchBar()
|
||||
}
|
||||
|
||||
//现实搜索栏
|
||||
@@ -217,13 +179,13 @@ class MainActivity : BaseActivity() {
|
||||
.setDuration(shortAnnotationDuration.toLong())
|
||||
.setListener(null)
|
||||
}
|
||||
barShow = true
|
||||
viewModel.uiState.value.switchShowSearchBar()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
if (barShow){
|
||||
if (viewModel.uiState.value.showSearchBar){
|
||||
hiddenSearchBar()
|
||||
}else {
|
||||
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
|
||||
@@ -231,8 +193,8 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
R.id.search_nav -> {
|
||||
if (barShow) {
|
||||
action(search.text.toString())
|
||||
if (viewModel.uiState.value.showSearchBar) {
|
||||
viewModel.fetchNewPostBySearch(search.text.toString())
|
||||
hideIm()
|
||||
|
||||
}
|
||||
@@ -250,78 +212,6 @@ class MainActivity : BaseActivity() {
|
||||
WindowCompat.getInsetsController(this.window,window.decorView).hide(ime())
|
||||
}
|
||||
|
||||
//执行加载
|
||||
private fun action(searchTag :String?) {
|
||||
var tag = searchTag
|
||||
var isNum = true
|
||||
try {
|
||||
tag?.toInt()
|
||||
}catch (e:NumberFormatException){
|
||||
isNum = false
|
||||
}
|
||||
|
||||
if (isNum){
|
||||
tag = "id:"+searchTag
|
||||
}
|
||||
|
||||
loadData(tag, 1, ISREFRESH)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//设置列表数据
|
||||
private fun loadData(tags: String?,page: Int,type:Int){
|
||||
//缓存搜索的tags
|
||||
this.tags = tags
|
||||
|
||||
val swipeRefreshLayout =
|
||||
findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(
|
||||
R.id.swipeRefreshLayout
|
||||
)
|
||||
swipeRefreshLayout.isRefreshing = true
|
||||
|
||||
//读取配置
|
||||
val configSp = getSharedPreferences("com.lsp.view_preferences", 0)
|
||||
val nowSourceName: String? = configSp.getString("source_name",null)
|
||||
var source = ""
|
||||
for ((index,sourceName) in sourceNameArray.withIndex()){
|
||||
if (sourceName == nowSourceName){
|
||||
source = sourceUrlArray[index]
|
||||
}
|
||||
}
|
||||
|
||||
//接收异步信息
|
||||
val handler = object : Handler(Looper.myLooper()!!){
|
||||
override fun handleMessage(msg: Message) {
|
||||
super.handleMessage(msg)
|
||||
when (msg.what){
|
||||
CallBackStatus.DATAISNULL.ordinal ->{
|
||||
Snackbar.make(swipeRefreshLayout,R.string.toast_load_empty,Snackbar.LENGTH_SHORT).show()
|
||||
search.setText("")
|
||||
}
|
||||
CallBackStatus.OK.ordinal -> {
|
||||
if (type == ISREFRESH )
|
||||
//刷新数据
|
||||
adapter.refreshData(msg.obj as ArrayList<YandPost>)
|
||||
else if (type == ISADDDATA)
|
||||
//加载数据
|
||||
adapter.addData(msg.obj as ArrayList<YandPost>)
|
||||
|
||||
}
|
||||
CallBackStatus.NETWORKERROR.ordinal -> {
|
||||
Snackbar.make(swipeRefreshLayout,R.string.toast_load_network_fail,Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
MainActivityModelImpl().requestPostList(handler,source,tags,page,configSp.getBoolean("safe_mode",true))
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.toolbar_menu, menu)
|
||||
|
||||
@@ -1,91 +1,105 @@
|
||||
package com.lsp.view.activity.main
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.LazyHeaders
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.activity.pic.PicActivity
|
||||
import com.lsp.view.bean.YandPost
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
|
||||
|
||||
class PostAdapter(val context: Context, private var postYandList: ArrayList<YandPost>) :
|
||||
class PostAdapter(val context: Context):
|
||||
RecyclerView.Adapter<PostAdapter.ViewHolder>() {
|
||||
val TAG = this::class.java.simpleName
|
||||
private val UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
|
||||
|
||||
private var postList = ArrayList<YandPost>()
|
||||
|
||||
|
||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val picImage: ImageView = view.findViewById(R.id.picImgae)
|
||||
|
||||
}
|
||||
|
||||
fun addData(list: ArrayList<YandPost>) {
|
||||
val pos = postYandList.size
|
||||
postYandList.addAll(list)
|
||||
notifyItemRangeInserted(pos, list.size)
|
||||
fun pushNewData(list: ArrayList<YandPost>) {
|
||||
val oldSize = postList.size
|
||||
postList.clear()
|
||||
notifyItemRangeRemoved(0,oldSize)
|
||||
postList.addAll(list)
|
||||
notifyItemRangeInserted(0, list.size)
|
||||
}
|
||||
|
||||
fun refreshData(list: ArrayList<YandPost>) {
|
||||
|
||||
val oldSize = postYandList.size
|
||||
postYandList.clear()
|
||||
notifyItemRangeRemoved(0,oldSize)
|
||||
postYandList.addAll(list)
|
||||
notifyItemRangeInserted(0, list.size)
|
||||
fun appendDate(list: ArrayList<YandPost>){
|
||||
val pos = postList.size
|
||||
postList.addAll(list)
|
||||
notifyItemRangeInserted(pos, list.size)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.img_item_layout, parent, false)
|
||||
val viewHolder = ViewHolder(view)
|
||||
|
||||
viewHolder.picImage.setOnClickListener {
|
||||
val position = viewHolder.adapterPosition
|
||||
Log.w("position", position.toString())
|
||||
Log.w("url", postYandList[position].sample_url)
|
||||
Log.w("rating", postYandList[position].rating)
|
||||
var file_ext = postYandList[position].file_ext
|
||||
// Log.w("position", position.toString())
|
||||
// Log.w("url", postList.value?.get(position)?.sample_url)
|
||||
// Log.w("rating", postList[position].rating)
|
||||
var file_ext = postList[position].file_ext
|
||||
|
||||
if (file_ext == null){
|
||||
val strarr = postYandList[position].sample_url.split(".")
|
||||
val strarr = postList[position].sample_url.split(".")
|
||||
file_ext = strarr[strarr.lastIndex]
|
||||
}
|
||||
|
||||
PicActivity.actionStartActivity(context,postYandList[position].id,postYandList[position].sample_url,
|
||||
postYandList[position].file_url,postYandList[position].tags,file_ext,postYandList[position].file_size,postYandList[position].md5,postYandList[position].sample_height,postYandList[position].sample_width)
|
||||
PicActivity.actionStartActivity(
|
||||
context,
|
||||
postList[position].id,
|
||||
postList[position].sample_url,
|
||||
postList[position].file_url,
|
||||
postList[position].tags,
|
||||
file_ext,
|
||||
postList[position].file_size,
|
||||
postList[position].md5,
|
||||
postList[position].sample_height,
|
||||
postList[position].sample_width)
|
||||
}
|
||||
|
||||
|
||||
return viewHolder
|
||||
}
|
||||
|
||||
private lateinit var mLoadMoreListener: OnLoadMoreListener
|
||||
private lateinit var mScrollToBottom: OnScrollToBottom
|
||||
|
||||
interface OnLoadMoreListener {
|
||||
fun loadMore(position: Int)
|
||||
interface OnScrollToBottom {
|
||||
fun event(position: Int)
|
||||
}
|
||||
|
||||
fun setLoadMoreListener(mLoadMoreListener: OnLoadMoreListener) {
|
||||
this.mLoadMoreListener = mLoadMoreListener
|
||||
|
||||
fun setLoadMoreListener(mLoadMoreListener: OnScrollToBottom) {
|
||||
this.mScrollToBottom = mLoadMoreListener
|
||||
}
|
||||
|
||||
private fun preLoad(p:Int){
|
||||
if (p%20==0){
|
||||
//执行预加载
|
||||
val last:Int = if (postYandList.size-p<20){
|
||||
postYandList.size-1
|
||||
val last:Int = if (postList.size-p<20){
|
||||
postList.size-1
|
||||
}else{
|
||||
p+19
|
||||
}
|
||||
for(index in p..last){
|
||||
if (p>6) {
|
||||
val source: String = postYandList[index].sample_url
|
||||
val source: String = postList[index].sample_url
|
||||
val glideUrl = GlideUrl(
|
||||
source,
|
||||
LazyHeaders.Builder().addHeader("User-Agent", UA)
|
||||
@@ -95,18 +109,19 @@ class PostAdapter(val context: Context, private var postYandList: ArrayList<Yand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
|
||||
preLoad(position)
|
||||
|
||||
val post = postYandList[position]
|
||||
val post = postList[position]
|
||||
|
||||
val source: String = post.sample_url
|
||||
|
||||
val height = postYandList[position].sample_height
|
||||
val width = postYandList[position].sample_width
|
||||
val height = postList[position].sample_height
|
||||
val width = postList[position].sample_width
|
||||
|
||||
//图片过长则缩减高度以保证显示完整
|
||||
if (width > height){
|
||||
@@ -123,15 +138,17 @@ class PostAdapter(val context: Context, private var postYandList: ArrayList<Yand
|
||||
)
|
||||
Glide.with(context).load(glideUrl).into(holder.picImage)
|
||||
|
||||
if (position == postYandList.size - 1 && postYandList.size > 6) {
|
||||
if (position == postList.size - 1 && postList.size > 6) {
|
||||
//到达底部
|
||||
mLoadMoreListener.loadMore(position)
|
||||
mScrollToBottom.event(position)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return postYandList.size
|
||||
val size = postList.size
|
||||
return size
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.lsp.view.activity.model
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Message
|
||||
import com.lsp.view.bean.Post
|
||||
import com.lsp.view.util.CallBackStatus
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
open class BaseModel {
|
||||
/**
|
||||
* @param service ArrayList接收边界为Post的泛型,若直接使用Post作为泛型参数,需要强制类型转换。
|
||||
*/
|
||||
fun <T : Post> request(service: Call<ArrayList<T>>, safeMode: Boolean, handler: Handler){
|
||||
|
||||
service.enqueue(object : Callback<ArrayList<T>> {
|
||||
override fun onResponse(call: Call<ArrayList<T>>, response: Response<ArrayList<T>>) {
|
||||
val getValue:ArrayList<T>? = response.body()
|
||||
|
||||
if (getValue!=null) {
|
||||
if (getValue.size==0){
|
||||
callBack(handler, CallBackStatus.DATAISNULL)
|
||||
return
|
||||
}
|
||||
if (safeMode) {
|
||||
val iter = getValue.iterator()
|
||||
while (iter.hasNext()) {
|
||||
val post = iter.next()
|
||||
if (post.rating != "s")
|
||||
iter.remove()
|
||||
}
|
||||
}
|
||||
callBack(handler, CallBackStatus.OK,getValue)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ArrayList<T>>, t: Throwable) {
|
||||
callBack(handler, CallBackStatus.NETWORKERROR)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
fun <T:Post> callBack(handler: Handler,status: CallBackStatus,value:ArrayList<T>){
|
||||
val msg = Message.obtain()
|
||||
msg.what = status.ordinal
|
||||
msg.obj = value
|
||||
handler.sendMessage(msg)
|
||||
}
|
||||
|
||||
fun callBack(handler: Handler,status: CallBackStatus){
|
||||
val msg = Message.obtain()
|
||||
msg.what = status.ordinal
|
||||
handler.sendMessage(msg)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.lsp.view.activity.model
|
||||
|
||||
import android.os.Handler
|
||||
|
||||
interface MainActivityModel {
|
||||
fun requestPostList(handler: Handler, source: String, tage: String?, page: Int, safeMode: Boolean)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.lsp.view.activity.model
|
||||
|
||||
import android.os.Handler
|
||||
import com.lsp.view.retrofit.PostService
|
||||
import com.lsp.view.retrofit.ServiceCreator
|
||||
import com.lsp.view.bean.YandPost
|
||||
import retrofit2.Call
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class MainActivityModelImpl :MainActivityModel, BaseModel() {
|
||||
private val TAG = this::class.java.simpleName
|
||||
override fun requestPostList(handler: Handler, source: String, tage: String?, page: Int,safeMode: Boolean){
|
||||
val postService: PostService = ServiceCreator.create(source)
|
||||
val service: Call<ArrayList<YandPost>> = postService.getPostData("100",tage, page)
|
||||
request(service,safeMode, handler)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.bean.ID
|
||||
import com.lsp.view.bean.Tags
|
||||
import com.lsp.view.repository.bean.ID
|
||||
import com.lsp.view.repository.bean.Tags
|
||||
|
||||
class IdAdapter(val idList: List<ID>,val context: Context) : RecyclerView.Adapter<IdAdapter.ViewHolder>() {
|
||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
@@ -27,9 +27,9 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.activity.BaseActivity
|
||||
import com.lsp.view.activity.main.MainActivity
|
||||
import com.lsp.view.bean.ID
|
||||
import com.lsp.view.bean.Size
|
||||
import com.lsp.view.bean.Tags
|
||||
import com.lsp.view.repository.bean.ID
|
||||
import com.lsp.view.repository.bean.Size
|
||||
import com.lsp.view.repository.bean.Tags
|
||||
import com.lsp.view.util.Util
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.bean.Size
|
||||
import com.lsp.view.repository.bean.Size
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.bean.Tags
|
||||
import com.lsp.view.repository.bean.Tags
|
||||
|
||||
class TagAdapter(val tagList: List<Tags>, val context: Context) :
|
||||
RecyclerView.Adapter<TagAdapter.ViewHolder>() {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.lsp.view.bean
|
||||
|
||||
class Author(val author: String)
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.lsp.view.bean
|
||||
|
||||
class ID(val id: String)
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.lsp.view.bean
|
||||
|
||||
class Size(val file_size: String)
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.lsp.view.bean
|
||||
|
||||
class Tags(val tag: String)
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.lsp.view.model
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Message
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AbstractSavedStateViewModelFactory
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lsp.view.activity.main.PostAdapter
|
||||
import com.lsp.view.repository.PostRepository
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
import com.lsp.view.repository.exception.NetworkErrorException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainViewModel(private val repository: PostRepository,private val context: Context):ViewModel() {
|
||||
private val _uiState = MutableStateFlow(UiState())
|
||||
val uiState:StateFlow<UiState> = _uiState.asStateFlow()
|
||||
private var fetchJob: Job? = null
|
||||
val adapter = PostAdapter(context)
|
||||
private val TAG = this::class.java.simpleName
|
||||
|
||||
companion object {
|
||||
fun provideFactory(
|
||||
repository: PostRepository,
|
||||
context: Context,
|
||||
owner: SavedStateRegistryOwner,
|
||||
defaultArgs: Bundle? = null,
|
||||
|
||||
): AbstractSavedStateViewModelFactory =
|
||||
object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
key: String,
|
||||
modelClass: Class<T>,
|
||||
handle: SavedStateHandle
|
||||
): T {
|
||||
return MainViewModel(repository, context) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
init {
|
||||
//初始化写入
|
||||
val configSp = context.getSharedPreferences("com.lsp.view_preferences", 0)
|
||||
if (configSp.getString("source_name", null) == null) {
|
||||
configSp.edit().putString("source_name", "yande.re").apply()
|
||||
configSp.edit().putString("type", "0").apply()
|
||||
}
|
||||
|
||||
_uiState.update {
|
||||
it.copy(nowSourceName = configSp.getString("source_name",null))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun fetchNewPost(){
|
||||
fetchJob?.cancel()
|
||||
fetchJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
_uiState.value.isRefreshing.postValue(true)
|
||||
|
||||
try {
|
||||
val postList = repository.fetchPostData(
|
||||
_uiState.value.nowSearchText,
|
||||
_uiState.value.isSafe,
|
||||
_uiState.value.nowPage
|
||||
)
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
if (_uiState.value.isAppend){
|
||||
adapter.appendDate(postList)
|
||||
}else{
|
||||
adapter.pushNewData(postList)
|
||||
}
|
||||
}
|
||||
|
||||
}catch (e:NetworkErrorException){
|
||||
launch(Dispatchers.Main){
|
||||
Toast.makeText(context,e.message,Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
_uiState.value.isRefreshing.postValue(false)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun fetchNewPostBySearch(target:String){
|
||||
_uiState.update {
|
||||
it.copy(nowSearchText = target)
|
||||
}
|
||||
|
||||
fetchNewPost()
|
||||
}
|
||||
|
||||
fun fetchPostByRefresh(){
|
||||
_uiState.update {
|
||||
it.copy(nowPage = 1)
|
||||
}
|
||||
|
||||
fetchNewPost()
|
||||
}
|
||||
|
||||
fun appendPost(){
|
||||
|
||||
_uiState.update {
|
||||
it.copy(isAppend = true)
|
||||
}
|
||||
|
||||
fetchNewPost()
|
||||
|
||||
_uiState.update {
|
||||
it.copy(isAppend = false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.lsp.view.model
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
||||
data class UiState(
|
||||
var showSearchBar:Boolean = false,
|
||||
val isRefreshing:MutableLiveData<Boolean> = MutableLiveData(),
|
||||
val nowSearchText:String = "",
|
||||
val nowSourceName: String? = null,
|
||||
val isSafe: Boolean = true, //安全模式
|
||||
val nowPage:Int = 1,
|
||||
val isAppend:Boolean = false
|
||||
){
|
||||
fun switchShowSearchBar(){
|
||||
showSearchBar = !showSearchBar
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.lsp.view.repository
|
||||
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.YandViewApplication
|
||||
import com.lsp.view.repository.exception.UnableConstructObjectException
|
||||
import com.lsp.view.repository.api.PostApi
|
||||
import com.lsp.view.repository.api.PostApiImpl
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
|
||||
class PostDataSource {
|
||||
private val api: PostApi = PostApiImpl()
|
||||
|
||||
|
||||
|
||||
//设置列表数据
|
||||
suspend fun fetchNewPost(load: Load): ArrayList<YandPost> {
|
||||
return api.fetchNewPost(load)
|
||||
}
|
||||
}
|
||||
data class Load(
|
||||
val tags: String?,
|
||||
val page: Int,
|
||||
val safe:Boolean
|
||||
){
|
||||
lateinit var source:String
|
||||
|
||||
companion object{
|
||||
fun Builder(tags: String?, page: Int, safe:Boolean):Load{
|
||||
val sourceNameArray = YandViewApplication.context!!.resources.getStringArray(R.array.pic_source)
|
||||
val sourceUrlArray = YandViewApplication.context!!.resources.getStringArray(R.array.url_source)
|
||||
val configSp = YandViewApplication.context?.getSharedPreferences("com.lsp.view_preferences", 0)
|
||||
val nowSourceName: String? = configSp?.getString("source_name",null)
|
||||
val load = Load(tags,page,safe)
|
||||
|
||||
//初始化数据
|
||||
for ((index,sourceName) in sourceNameArray.withIndex()){
|
||||
if (sourceName == nowSourceName){
|
||||
load.source = sourceUrlArray[index]
|
||||
return load
|
||||
}
|
||||
}
|
||||
|
||||
throw UnableConstructObjectException("Unable construct load object.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.lsp.view.repository
|
||||
|
||||
import android.os.Handler
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
|
||||
|
||||
class PostRepository {
|
||||
private val dataSource = PostDataSource()
|
||||
|
||||
//获取post
|
||||
suspend fun fetchPostData(searchTarget :String?,safe:Boolean,page:Int): ArrayList<YandPost> {
|
||||
|
||||
var target = searchTarget
|
||||
var isNum = true
|
||||
|
||||
//如果检索目标是纯数字,则按照id搜索
|
||||
try {
|
||||
target?.toLong()
|
||||
}catch (e:NumberFormatException){
|
||||
isNum = false
|
||||
}
|
||||
|
||||
if (isNum){
|
||||
target = "id:$searchTarget"
|
||||
}
|
||||
|
||||
val load = Load.Builder(target,page,safe)
|
||||
|
||||
return dataSource.fetchNewPost(load)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.lsp.view.repository.api
|
||||
|
||||
import android.util.Log
|
||||
import com.lsp.view.repository.Load
|
||||
import com.lsp.view.repository.bean.Post
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
import com.lsp.view.repository.exception.NetworkErrorException
|
||||
import retrofit2.Call
|
||||
import java.net.SocketException
|
||||
|
||||
interface PostApi {
|
||||
private val TAG: String
|
||||
get() = this::class.java.simpleName
|
||||
|
||||
/**
|
||||
* @param service ArrayList接收边界为Post的泛型,若直接使用Post作为泛型参数,需要强制类型转换。
|
||||
*/
|
||||
@Throws(NetworkErrorException::class)
|
||||
fun <T : Post> request(service: Call<ArrayList<T>>, safeMode: Boolean):ArrayList<T> {
|
||||
|
||||
try {
|
||||
val execute = service.execute()
|
||||
return if (execute.isSuccessful) {
|
||||
if (execute.body() != null) {
|
||||
Log.e(TAG, execute.body().toString())
|
||||
execute.body()!!
|
||||
|
||||
} else {
|
||||
//结果为空,返回空集避免空指针
|
||||
ArrayList()
|
||||
}
|
||||
} else {
|
||||
throw NetworkErrorException(execute.errorBody().toString())
|
||||
}
|
||||
|
||||
}catch (e: Exception){
|
||||
throw NetworkErrorException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchNewPost(load:Load):ArrayList<YandPost>
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.lsp.view.repository.api
|
||||
|
||||
import com.lsp.view.repository.Load
|
||||
import com.lsp.view.retrofit.PostService
|
||||
import com.lsp.view.retrofit.ServiceCreator
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
import retrofit2.Call
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class PostApiImpl :PostApi {
|
||||
private val TAG = this::class.java.simpleName
|
||||
private val defaultLimit = "100"
|
||||
override suspend fun fetchNewPost(load: Load):ArrayList<YandPost>{
|
||||
val postService: PostService = ServiceCreator.create(load.source)
|
||||
val service: Call<ArrayList<YandPost>> = postService.getPostData(defaultLimit,load.tags, load.page)
|
||||
|
||||
return request(service, load.safe)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.lsp.view.repository.bean
|
||||
|
||||
class Author(val author: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.lsp.view.repository.bean
|
||||
|
||||
class ID(val id: String)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.lsp.view.bean
|
||||
package com.lsp.view.repository.bean
|
||||
|
||||
open class Post(
|
||||
val id: String,
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.lsp.view.repository.bean
|
||||
|
||||
class Size(val file_size: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.lsp.view.repository.bean
|
||||
|
||||
class Tags(val tag: String)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.lsp.view.bean
|
||||
package com.lsp.view.repository.bean
|
||||
|
||||
class YandPost(
|
||||
val preview_url: String,
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.lsp.view.repository.exception
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
|
||||
class NetworkErrorException(override val message: String?) : Exception() {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.lsp.view.repository.exception
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
class UnableConstructObjectException(override val message: String?):Exception(message) {
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.lsp.view.retrofit
|
||||
|
||||
import com.lsp.view.bean.YandPost
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
@@ -11,13 +11,11 @@ object ServiceCreator {
|
||||
* @param source:加载根地址
|
||||
*
|
||||
*/
|
||||
var source: String? = null
|
||||
fun <T> create(serviceClass: Class<T>, source: String): T {
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(source)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
ServiceCreator.source = source
|
||||
return retrofit.create(serviceClass)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.*
|
||||
import com.lsp.view.util.CallBackStatus
|
||||
import com.lsp.view.util.Util
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.io.File
|
||||
@@ -19,7 +19,7 @@ class DownloadService : Service() {
|
||||
|
||||
class DownloadBinder(val context: Context) : Binder() {
|
||||
|
||||
fun callBack(handler: Handler,status:CallBackStatus){
|
||||
fun callBack(handler: Handler,status:Int){
|
||||
val msg = Message.obtain()
|
||||
msg.obj = status
|
||||
handler.sendMessage(msg)
|
||||
@@ -35,7 +35,7 @@ class DownloadService : Service() {
|
||||
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$md5.$end")
|
||||
|
||||
if (file.exists()){
|
||||
callBack(handler,CallBackStatus.FILEEXISTS)
|
||||
callBack(handler,Util.FILE_EXISTS)
|
||||
return@thread
|
||||
}
|
||||
|
||||
@@ -57,24 +57,24 @@ class DownloadService : Service() {
|
||||
null, null
|
||||
)
|
||||
if (md5 == getFileMD5(file.path)) {
|
||||
callBack(handler, CallBackStatus.DOWNLOADSUCCESS)
|
||||
callBack(handler, Util.DOWNLOAD_SUCCESS)
|
||||
} else {
|
||||
file.delete()
|
||||
callBack(handler,CallBackStatus.MD5COMPAREERROR)
|
||||
callBack(handler,Util.MD5_COMPARE_ERROR)
|
||||
}
|
||||
|
||||
return@thread
|
||||
|
||||
} else {
|
||||
file.delete()
|
||||
callBack(handler, CallBackStatus.NETWORKERROR)
|
||||
callBack(handler, Util.NETWORK_ERROR)
|
||||
return@thread
|
||||
}
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
callBack(handler, CallBackStatus.DOWNLOADERROR)
|
||||
callBack(handler, Util.DOWNLOAD_ERROR)
|
||||
return@thread
|
||||
} finally {
|
||||
fos.close()
|
||||
@@ -84,7 +84,7 @@ class DownloadService : Service() {
|
||||
FileD.mkdirs()
|
||||
downloadPic(file_url, end, handler, md5)
|
||||
}
|
||||
callBack(handler,CallBackStatus.DOWNLOADERROR)
|
||||
callBack(handler,Util.DOWNLOAD_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.lsp.view.util
|
||||
|
||||
enum class CallBackStatus {
|
||||
OK, NETWORKERROR, DATAISNULL, DOWNLOADERROR, SETWP, MD5COMPAREERROR, FILEEXISTS, DOWNLOADSUCCESS
|
||||
}
|
||||
@@ -18,6 +18,12 @@ import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
object Util {
|
||||
const val NETWORK_ERROR: Int = 0
|
||||
const val DOWNLOAD_SUCCESS = 1
|
||||
const val DOWNLOAD_ERROR = 2
|
||||
const val MD5_COMPARE_ERROR = 3
|
||||
const val FILE_EXISTS = 4
|
||||
|
||||
fun share(url:String,context: Context){
|
||||
thread {
|
||||
val bitmap = Glide.with(context).asBitmap().load(url).submit().get()
|
||||
@@ -83,19 +89,19 @@ object Util {
|
||||
override fun handleMessage(msg: Message) {
|
||||
super.handleMessage(msg)
|
||||
when(msg.obj){
|
||||
CallBackStatus.DOWNLOADSUCCESS ->{
|
||||
DOWNLOAD_SUCCESS ->{
|
||||
Toast.makeText(YandViewApplication.context, R.string.toast_download_success, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
CallBackStatus.DOWNLOADERROR.ordinal -> {
|
||||
DOWNLOAD_ERROR -> {
|
||||
Toast.makeText(YandViewApplication.context, R.string.toast_download_fail, Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
CallBackStatus.MD5COMPAREERROR.ordinal -> {
|
||||
MD5_COMPARE_ERROR -> {
|
||||
Toast.makeText(YandViewApplication.context, R.string.toast_compar_md5_fail, Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
CallBackStatus.FILEEXISTS.ordinal -> {
|
||||
FILE_EXISTS -> {
|
||||
Toast.makeText(YandViewApplication.context, R.string.toast_file_exist, Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user