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 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,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,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
}
+10 -4
View File
@@ -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()
}