refactor:Use viewModel

This commit is contained in:
AnranYus
2023-10-20 01:33:50 +08:00
parent bbd4c3b7d1
commit 25046d22a8
35 changed files with 438 additions and 311 deletions
@@ -11,6 +11,7 @@ import com.google.android.material.color.DynamicColors
import android.content.Intent import android.content.Intent
import com.lsp.view.service.DownloadService import com.lsp.view.service.DownloadService
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.lsp.view.repository.PostRepository
class YandViewApplication : Application(), DefaultLifecycleObserver { class YandViewApplication : Application(), DefaultLifecycleObserver {
private val connection: ServiceConnection = object : ServiceConnection { private val connection: ServiceConnection = object : ServiceConnection {
@@ -20,6 +21,7 @@ class YandViewApplication : Application(), DefaultLifecycleObserver {
override fun onServiceDisconnected(componentName: ComponentName) {} override fun onServiceDisconnected(componentName: ComponentName) {}
} }
val repository = PostRepository()
override fun onCreate() { override fun onCreate() {
super<Application>.onCreate() super<Application>.onCreate()
@@ -15,7 +15,7 @@ import com.lsp.view.YandViewApplication
import com.lsp.view.R import com.lsp.view.R
import com.lsp.view.activity.BaseActivity import com.lsp.view.activity.BaseActivity
import com.lsp.view.activity.main.MainActivity import com.lsp.view.activity.main.MainActivity
import com.lsp.view.bean.Tags import com.lsp.view.repository.bean.Tags
class FavTagActivity : BaseActivity() { class FavTagActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.lsp.view.R 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) : class FavTagAdapter(val tagList: ArrayList<Tags>, val context: Context) :
RecyclerView.Adapter<FavTagAdapter.ViewHolder>() { RecyclerView.Adapter<FavTagAdapter.ViewHolder>() {
@@ -4,11 +4,13 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.content.Intent import android.content.Intent
import android.os.* import android.os.*
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.EditText import android.widget.EditText
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
@@ -18,100 +20,64 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import com.lsp.view.YandViewApplication import com.lsp.view.YandViewApplication
import com.lsp.view.R import com.lsp.view.R
import com.lsp.view.activity.BaseActivity import com.lsp.view.activity.BaseActivity
import com.lsp.view.activity.favtag.FavTagActivity 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.activity.setting.SettingsActivity
import com.lsp.view.bean.YandPost import com.lsp.view.model.MainViewModel
import com.lsp.view.util.CallBackStatus
class MainActivity : BaseActivity() { class MainActivity : BaseActivity() {
private lateinit var search: EditText private lateinit var search: EditText
private var shortAnnotationDuration: Int = 0 private var shortAnnotationDuration: Int = 0
private var nowPage = 1
private val adapter: PostAdapter = PostAdapter(this, ArrayList())
private var username: String? = "" private var username: String? = ""
private lateinit var sourceUrlArray: Array<String>
private lateinit var sourceNameArray: Array<String>
private var nowSourceName: String? = null private var nowSourceName: String? = null
val TAG = javaClass.simpleName val TAG = javaClass.simpleName
private lateinit var layoutManager: RecyclerView.LayoutManager
private var safeMode: Boolean = true //安全模式 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 toolbar:Toolbar
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
viewModel = MainViewModel.provideFactory((application as YandViewApplication).repository, this,this).create(MainViewModel::class.java)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
val appbar = findViewById<AppBarLayout>(R.id.appbar) val appbar = findViewById<AppBarLayout>(R.id.appbar)
val nowHeight = appbar.layoutParams.height val nowHeight = appbar.layoutParams.height
appbar.layoutParams.height = (application as YandViewApplication).statusBarHeight()+nowHeight appbar.layoutParams.height = (application as YandViewApplication).statusBarHeight()+nowHeight
//刷新
val refresh =
findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(R.id.swipeRefreshLayout)
//到达底部,加载更多数据 viewModel.uiState.value.isRefreshing.observe(this) {
adapter.setLoadMoreListener(object : PostAdapter.OnLoadMoreListener { refresh.isRefreshing = it
//重写接口
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()
} }
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) 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 { supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true) it.setDisplayHomeAsUpEnabled(true)
@@ -122,17 +88,12 @@ class MainActivity : BaseActivity() {
search.setOnEditorActionListener { _, actionId, _ -> search.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) { if (actionId == EditorInfo.IME_ACTION_SEARCH) {
action(search.text.toString()) viewModel.fetchNewPostBySearch(search.text.toString())
} }
return@setOnEditorActionListener false return@setOnEditorActionListener false
} }
//刷新
swipeRefreshLayout.setOnRefreshListener {
nowPage = 1
loadData(tags, nowPage, ISREFRESH)
}
//侧边栏 //侧边栏
val sp = getSharedPreferences("username", 0) val sp = getSharedPreferences("username", 0)
@@ -144,10 +105,11 @@ class MainActivity : BaseActivity() {
nav.setCheckedItem(R.id.photo) nav.setCheckedItem(R.id.photo)
//设置侧边栏点击逻辑 //设置侧边栏点击逻辑
nav.setNavigationItemSelectedListener { nav.setNavigationItemSelectedListener {
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
when (it.itemId) { when (it.itemId) {
//画廊 //画廊
R.id.photo -> { R.id.photo -> {
loadData( null, 1, ISREFRESH) viewModel.fetchPostByRefresh()
drawerLayout.closeDrawers() drawerLayout.closeDrawers()
true true
} }
@@ -174,15 +136,16 @@ class MainActivity : BaseActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
//加载源改变 //加载源改变
val configSp = getSharedPreferences("com.lsp.view_preferences", 0) val configSp = getSharedPreferences("com.lsp.view_preferences", 0)
if (nowSourceName != configSp.getString("source_name",null)){ if (nowSourceName != configSp.getString("source_name",null)){
action("") viewModel.fetchPostByRefresh()
nowSourceName = configSp.getString("source_name",null) nowSourceName = configSp.getString("source_name",null)
} }
if (safeMode != configSp.getBoolean("safe_mode",true)){ if (safeMode != configSp.getBoolean("safe_mode",true)){
action("") viewModel.fetchPostByRefresh()
safeMode = configSp.getBoolean("safe_mode",true) safeMode = configSp.getBoolean("safe_mode",true)
} }
@@ -202,8 +165,7 @@ class MainActivity : BaseActivity() {
} }
}) })
hideIm() hideIm()
barShow = false viewModel.uiState.value.switchShowSearchBar()
} }
//现实搜索栏 //现实搜索栏
@@ -217,13 +179,13 @@ class MainActivity : BaseActivity() {
.setDuration(shortAnnotationDuration.toLong()) .setDuration(shortAnnotationDuration.toLong())
.setListener(null) .setListener(null)
} }
barShow = true viewModel.uiState.value.switchShowSearchBar()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
if (barShow){ if (viewModel.uiState.value.showSearchBar){
hiddenSearchBar() hiddenSearchBar()
}else { }else {
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout) val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
@@ -231,8 +193,8 @@ class MainActivity : BaseActivity() {
} }
} }
R.id.search_nav -> { R.id.search_nav -> {
if (barShow) { if (viewModel.uiState.value.showSearchBar) {
action(search.text.toString()) viewModel.fetchNewPostBySearch(search.text.toString())
hideIm() hideIm()
} }
@@ -250,78 +212,6 @@ class MainActivity : BaseActivity() {
WindowCompat.getInsetsController(this.window,window.decorView).hide(ime()) 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 { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_menu, menu) menuInflater.inflate(R.menu.toolbar_menu, menu)
@@ -1,91 +1,105 @@
package com.lsp.view.activity.main package com.lsp.view.activity.main
import android.content.Context 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders import com.bumptech.glide.load.model.LazyHeaders
import com.lsp.view.R import com.lsp.view.R
import com.lsp.view.activity.pic.PicActivity 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>() { RecyclerView.Adapter<PostAdapter.ViewHolder>() {
val TAG = this::class.java.simpleName 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 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) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val picImage: ImageView = view.findViewById(R.id.picImgae) val picImage: ImageView = view.findViewById(R.id.picImgae)
} }
fun addData(list: ArrayList<YandPost>) { fun pushNewData(list: ArrayList<YandPost>) {
val pos = postYandList.size val oldSize = postList.size
postYandList.addAll(list) postList.clear()
notifyItemRangeInserted(pos, list.size) notifyItemRangeRemoved(0,oldSize)
postList.addAll(list)
notifyItemRangeInserted(0, list.size)
} }
fun refreshData(list: ArrayList<YandPost>) { fun appendDate(list: ArrayList<YandPost>){
val pos = postList.size
val oldSize = postYandList.size postList.addAll(list)
postYandList.clear() notifyItemRangeInserted(pos, list.size)
notifyItemRangeRemoved(0,oldSize)
postYandList.addAll(list)
notifyItemRangeInserted(0, list.size)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.img_item_layout, parent, false) val view = LayoutInflater.from(context).inflate(R.layout.img_item_layout, parent, false)
val viewHolder = ViewHolder(view) val viewHolder = ViewHolder(view)
viewHolder.picImage.setOnClickListener { viewHolder.picImage.setOnClickListener {
val position = viewHolder.adapterPosition val position = viewHolder.adapterPosition
Log.w("position", position.toString()) // Log.w("position", position.toString())
Log.w("url", postYandList[position].sample_url) // Log.w("url", postList.value?.get(position)?.sample_url)
Log.w("rating", postYandList[position].rating) // Log.w("rating", postList[position].rating)
var file_ext = postYandList[position].file_ext var file_ext = postList[position].file_ext
if (file_ext == null){ if (file_ext == null){
val strarr = postYandList[position].sample_url.split(".") val strarr = postList[position].sample_url.split(".")
file_ext = strarr[strarr.lastIndex] file_ext = strarr[strarr.lastIndex]
} }
PicActivity.actionStartActivity(context,postYandList[position].id,postYandList[position].sample_url, PicActivity.actionStartActivity(
postYandList[position].file_url,postYandList[position].tags,file_ext,postYandList[position].file_size,postYandList[position].md5,postYandList[position].sample_height,postYandList[position].sample_width) 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 return viewHolder
} }
private lateinit var mLoadMoreListener: OnLoadMoreListener private lateinit var mScrollToBottom: OnScrollToBottom
interface OnLoadMoreListener { interface OnScrollToBottom {
fun loadMore(position: Int) fun event(position: Int)
} }
fun setLoadMoreListener(mLoadMoreListener: OnLoadMoreListener) { fun setLoadMoreListener(mLoadMoreListener: OnScrollToBottom) {
this.mLoadMoreListener = mLoadMoreListener this.mScrollToBottom = mLoadMoreListener
} }
private fun preLoad(p:Int){ private fun preLoad(p:Int){
if (p%20==0){ if (p%20==0){
//执行预加载 //执行预加载
val last:Int = if (postYandList.size-p<20){ val last:Int = if (postList.size-p<20){
postYandList.size-1 postList.size-1
}else{ }else{
p+19 p+19
} }
for(index in p..last){ for(index in p..last){
if (p>6) { if (p>6) {
val source: String = postYandList[index].sample_url val source: String = postList[index].sample_url
val glideUrl = GlideUrl( val glideUrl = GlideUrl(
source, source,
LazyHeaders.Builder().addHeader("User-Agent", UA) 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) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
preLoad(position) preLoad(position)
val post = postYandList[position] val post = postList[position]
val source: String = post.sample_url val source: String = post.sample_url
val height = postYandList[position].sample_height val height = postList[position].sample_height
val width = postYandList[position].sample_width val width = postList[position].sample_width
//图片过长则缩减高度以保证显示完整 //图片过长则缩减高度以保证显示完整
if (width > height){ 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) 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 { 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.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.lsp.view.R import com.lsp.view.R
import com.lsp.view.bean.ID import com.lsp.view.repository.bean.ID
import com.lsp.view.bean.Tags import com.lsp.view.repository.bean.Tags
class IdAdapter(val idList: List<ID>,val context: Context) : RecyclerView.Adapter<IdAdapter.ViewHolder>() { class IdAdapter(val idList: List<ID>,val context: Context) : RecyclerView.Adapter<IdAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 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.R
import com.lsp.view.activity.BaseActivity import com.lsp.view.activity.BaseActivity
import com.lsp.view.activity.main.MainActivity import com.lsp.view.activity.main.MainActivity
import com.lsp.view.bean.ID import com.lsp.view.repository.bean.ID
import com.lsp.view.bean.Size import com.lsp.view.repository.bean.Size
import com.lsp.view.bean.Tags import com.lsp.view.repository.bean.Tags
import com.lsp.view.util.Util import com.lsp.view.util.Util
import kotlin.properties.Delegates import kotlin.properties.Delegates
@@ -6,7 +6,7 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lsp.view.R import com.lsp.view.R
import com.lsp.view.bean.Size import com.lsp.view.repository.bean.Size
import java.math.RoundingMode import java.math.RoundingMode
import java.text.DecimalFormat import java.text.DecimalFormat
@@ -10,7 +10,7 @@ import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.lsp.view.R 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) : class TagAdapter(val tagList: List<Tags>, val context: Context) :
RecyclerView.Adapter<TagAdapter.ViewHolder>() { 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,4 +1,4 @@
package com.lsp.view.bean package com.lsp.view.repository.bean
open class Post( open class Post(
val id: String, 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,4 +1,4 @@
package com.lsp.view.bean package com.lsp.view.repository.bean
class YandPost( class YandPost(
val preview_url: String, 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 package com.lsp.view.retrofit
import com.lsp.view.bean.YandPost import com.lsp.view.repository.bean.YandPost
import retrofit2.Call import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Query import retrofit2.http.Query
@@ -11,13 +11,11 @@ object ServiceCreator {
* @param source:加载根地址 * @param source:加载根地址
* *
*/ */
var source: String? = null
fun <T> create(serviceClass: Class<T>, source: String): T { fun <T> create(serviceClass: Class<T>, source: String): T {
val retrofit = Retrofit.Builder() val retrofit = Retrofit.Builder()
.baseUrl(source) .baseUrl(source)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.build() .build()
ServiceCreator.source = source
return retrofit.create(serviceClass) return retrofit.create(serviceClass)
} }
@@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.os.* import android.os.*
import com.lsp.view.util.CallBackStatus import com.lsp.view.util.Util
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.io.File import java.io.File
@@ -19,7 +19,7 @@ class DownloadService : Service() {
class DownloadBinder(val context: Context) : Binder() { class DownloadBinder(val context: Context) : Binder() {
fun callBack(handler: Handler,status:CallBackStatus){ fun callBack(handler: Handler,status:Int){
val msg = Message.obtain() val msg = Message.obtain()
msg.obj = status msg.obj = status
handler.sendMessage(msg) handler.sendMessage(msg)
@@ -35,7 +35,7 @@ class DownloadService : Service() {
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$md5.$end") File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$md5.$end")
if (file.exists()){ if (file.exists()){
callBack(handler,CallBackStatus.FILEEXISTS) callBack(handler,Util.FILE_EXISTS)
return@thread return@thread
} }
@@ -57,24 +57,24 @@ class DownloadService : Service() {
null, null null, null
) )
if (md5 == getFileMD5(file.path)) { if (md5 == getFileMD5(file.path)) {
callBack(handler, CallBackStatus.DOWNLOADSUCCESS) callBack(handler, Util.DOWNLOAD_SUCCESS)
} else { } else {
file.delete() file.delete()
callBack(handler,CallBackStatus.MD5COMPAREERROR) callBack(handler,Util.MD5_COMPARE_ERROR)
} }
return@thread return@thread
} else { } else {
file.delete() file.delete()
callBack(handler, CallBackStatus.NETWORKERROR) callBack(handler, Util.NETWORK_ERROR)
return@thread return@thread
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
callBack(handler, CallBackStatus.DOWNLOADERROR) callBack(handler, Util.DOWNLOAD_ERROR)
return@thread return@thread
} finally { } finally {
fos.close() fos.close()
@@ -84,7 +84,7 @@ class DownloadService : Service() {
FileD.mkdirs() FileD.mkdirs()
downloadPic(file_url, end, handler, md5) 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
}
+10 -4
View File
@@ -18,6 +18,12 @@ import java.io.File
import kotlin.concurrent.thread import kotlin.concurrent.thread
object Util { 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){ fun share(url:String,context: Context){
thread { thread {
val bitmap = Glide.with(context).asBitmap().load(url).submit().get() val bitmap = Glide.with(context).asBitmap().load(url).submit().get()
@@ -83,19 +89,19 @@ object Util {
override fun handleMessage(msg: Message) { override fun handleMessage(msg: Message) {
super.handleMessage(msg) super.handleMessage(msg)
when(msg.obj){ when(msg.obj){
CallBackStatus.DOWNLOADSUCCESS ->{ DOWNLOAD_SUCCESS ->{
Toast.makeText(YandViewApplication.context, R.string.toast_download_success, Toast.LENGTH_SHORT).show() 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() 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() 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() Toast.makeText(YandViewApplication.context, R.string.toast_file_exist, Toast.LENGTH_SHORT).show()
} }