refactor: To material3

This commit is contained in:
AnranYus
2023-11-06 23:04:03 +08:00
parent e3336b0458
commit b702e95f40
73 changed files with 1065 additions and 1487 deletions
+15 -2
View File
@@ -1,6 +1,9 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs.kotlin'
id "com.google.devtools.ksp"
}
android {
@@ -33,9 +36,17 @@ android {
}
dependencies {
def room_version = "2.5.0"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
ksp "androidx.room:room-compiler:$room_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
implementation("com.google.android.material:material:1.10.0")
implementation 'jp.wasabeef:recyclerview-animators:4.0.2'
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation ('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0')
@@ -43,8 +54,10 @@ dependencies {
implementation ('com.squareup.retrofit2:converter-gson:2.9.0')
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
implementation "androidx.navigation:navigation-ui-ktx:2.7.5"
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0"
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+2 -10
View File
@@ -23,12 +23,8 @@
android:resource="@xml/file_paths">
</meta-data>
</provider>
<activity
android:name=".activity.favtag.FavTagActivity"
android:exported="false" />
<activity
android:name=".activity.setting.SettingsActivity"
android:name=".activity.SettingsActivity"
android:exported="false"
android:label="@string/title_activity_settings" />
@@ -36,12 +32,8 @@
android:name=".service.DownloadService"
android:enabled="true"
android:exported="true" />
<activity
android:name=".activity.pic.PicActivity"
android:configChanges="uiMode"/>
<activity
android:name=".activity.main.MainActivity"
android:name=".activity.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -9,11 +9,13 @@ import android.os.IBinder
import com.lsp.view.service.DownloadService.DownloadBinder
import com.google.android.material.color.DynamicColors
import android.content.Intent
import android.util.Log
import com.lsp.view.service.DownloadService
import androidx.lifecycle.LifecycleOwner
import com.lsp.view.repository.PostRepository
class YandViewApplication : Application(), DefaultLifecycleObserver {
lateinit var downloadBinder:DownloadBinder
private val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
downloadBinder = iBinder as DownloadBinder
@@ -21,14 +23,14 @@ class YandViewApplication : Application(), DefaultLifecycleObserver {
override fun onServiceDisconnected(componentName: ComponentName) {}
}
val repository = PostRepository()
override fun onCreate() {
super<Application>.onCreate()
context = applicationContext
DynamicColors.applyToActivitiesIfAvailable(this)
val serviceIntent = Intent(this, DownloadService::class.java)
bindService(serviceIntent, connection, BIND_AUTO_CREATE)
DynamicColors.applyToActivitiesIfAvailable(this)
}
override fun onDestroy(owner: LifecycleOwner) {
@@ -39,7 +41,7 @@ class YandViewApplication : Application(), DefaultLifecycleObserver {
companion object {
var context: Context? = null
private set
var downloadBinder: DownloadBinder? = null
private set
const 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"
}
}
@@ -0,0 +1,66 @@
package com.lsp.view.activity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.lsp.view.R
import com.lsp.view.model.MainViewModel
import com.lsp.view.repository.network.PostRepository
class MainActivity : AppCompatActivity() {
val TAG = javaClass.simpleName
lateinit var viewModel: MainViewModel
lateinit var bottomNav:BottomNavigationView
val repository = PostRepository()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = MainViewModel.provideFactory(repository, this,this).create(MainViewModel::class.java)
bottomNav = findViewById(R.id.bottom_navigation)
viewModel.toastMessage.observe(this){
Toast.makeText(this,it,Toast.LENGTH_SHORT).show()
}
//接收来自PicActivity的快捷搜索Tag
val stringExtra = intent.getStringExtra("searchTag")
if (stringExtra!=null){
viewModel.uiState.value.nowSearchText.value = stringExtra
}
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
bottomNav.setupWithNavController(navController)
}
override fun onResume() {
super.onResume()
//加载源改变
val configSp = getSharedPreferences("com.lsp.view_preferences", 0)
val nowSourceName = configSp.getString("source_name", null)
if (nowSourceName != null && viewModel.uiState.value.nowSourceName.value != nowSourceName){
viewModel.updateNowSource(nowSourceName)
}
val nowMode = configSp.getBoolean("safe_mode", true)
if (viewModel.uiState.value.isSafe != nowMode){
viewModel.updateSafeMode(nowMode)
Log.e(TAG,nowMode.toString())
}
}
}
@@ -1,4 +1,4 @@
package com.lsp.view.activity.setting
package com.lsp.view.activity
import android.os.Bundle
import android.view.MenuItem
@@ -1,78 +0,0 @@
package com.lsp.view.activity.favtag
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.RecyclerView
import com.google.android.flexbox.*
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.lsp.view.R
import com.lsp.view.activity.main.MainActivity
import com.lsp.view.repository.bean.Tags
class FavTagActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fav_tag)
//Toolbar相关
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
val recyclerViewTag = findViewById<RecyclerView>(R.id.tag_recy)
recyclerViewTag.layoutManager = layoutManager()
val tagsArraySp = getSharedPreferences("tags_sp", Context.MODE_PRIVATE)
val array = tagsArraySp.getString("array", null)
var favTagList: ArrayList<Tags> = ArrayList()
if (array != null) {
//有收藏内容
val tagListType = object : TypeToken<ArrayList<Tags>>() {}.type
favTagList = Gson().fromJson(array, tagListType)
}
val favTagAdapter = FavTagAdapter(favTagList, this)
favTagAdapter.setOnItemClickListener(object : FavTagAdapter.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
val intent = Intent(this@FavTagActivity, MainActivity::class.java)
intent.putExtra("searchTag", favTagList[position].tag)
startActivity(intent)
}
})
recyclerViewTag.adapter = favTagAdapter
}
private fun layoutManager(): FlexboxLayoutManager {
val manager = object : FlexboxLayoutManager(this, FlexDirection.ROW, FlexWrap.WRAP) {
override fun canScrollVertically(): Boolean {
return false
}
}
manager.flexWrap = FlexWrap.WRAP
manager.flexDirection = FlexDirection.ROW
manager.alignItems = AlignItems.CENTER
manager.justifyContent = JustifyContent.FLEX_START
return manager
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
finish()
}
}
return super.onOptionsItemSelected(item)
}
}
@@ -1,72 +0,0 @@
package com.lsp.view.activity.favtag
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
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.repository.bean.Tags
class FavTagAdapter(val tagList: ArrayList<Tags>, val context: Context) :
RecyclerView.Adapter<FavTagAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val fav_tag = view.findViewById<TextView>(R.id.fav_tag)
}
private lateinit var mOnItemClickListener: OnItemClickListener
interface OnItemClickListener {
fun onItemClick(view: View, position: Int)
}
fun setOnItemClickListener(mOnItemClickListener: OnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.fav_tag_item_layout, parent, false)
val viewHolder = ViewHolder(view)
viewHolder.fav_tag.setOnLongClickListener {
val position = viewHolder.adapterPosition
removeData(position)
true
}
return viewHolder
}
fun removeData(position: Int) {
tagList.removeAt(position)
notifyItemRangeInserted(0, tagList.size)
val tagsArraySp = context.getSharedPreferences("tags_sp", Context.MODE_PRIVATE)
val tagsArrayJson = tagsArraySp.getString("array", null)
val tagListType = object : TypeToken<ArrayList<Tags>>() {}.type
val tagArray: ArrayList<Tags> = Gson().fromJson(tagsArrayJson, tagListType)
tagArray.removeAt(position)
tagsArraySp.edit().putString("array", Gson().toJson(tagArray)).apply()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tag = tagList[position].tag
holder.fav_tag.text = tag
holder.fav_tag.setOnClickListener {
mOnItemClickListener.onItemClick(it, position)
}
}
override fun getItemCount(): Int {
return tagList.size
}
}
@@ -1,130 +0,0 @@
package com.lsp.view.activity.main
import android.os.Bundle
import android.util.Log
import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import com.google.android.material.snackbar.Snackbar
import com.lsp.view.R
import com.lsp.view.YandViewApplication
import com.lsp.view.model.MainViewModel
class MainActivity : AppCompatActivity() {
private var shortAnnotationDuration: Int = 0
private var username: String? = ""
val TAG = javaClass.simpleName
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)
val adapter by lazy {
viewModel.fetchPostByRefresh()
PostAdapter(this)
}
//刷新
val refresh =
findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(R.id.swipeRefreshLayout)
viewModel.uiState.value.isRefreshing.observe(this) {
refresh.isRefreshing = it
}
viewModel.errorMessage.observe(this){
Snackbar.make(refresh,it,Snackbar.LENGTH_SHORT).show()
}
viewModel.postList.observe(this){
if (adapter.isAppendData){
adapter.appendDate(it)
}else{
adapter.pushNewData(it)
}
}
shortAnnotationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
val searchBar = findViewById<SearchBar>(R.id.search_bar)
val searchView = findViewById<SearchView>(R.id.search_view)
searchView.editText.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
viewModel.fetchPostBySearch(searchView.text.toString())
searchBar.setText(searchView.text)
searchView.hide()
}
return@setOnEditorActionListener false
}
//接收来自PicActivity的快捷搜索Tag
val stringExtra = intent.getStringExtra("searchTag")
if (stringExtra!=null){
viewModel.uiState.value.nowSearchText.value = stringExtra
}
viewModel.uiState.value.nowSearchText.observe(this){
searchView.setText(it)
searchBar.setText(it)
}
refresh.setOnRefreshListener {
viewModel.fetchPostByRefresh()
}
adapter.apply {
setLoadMoreListener(object :PostAdapter.OnScrollToBottom{
override fun event(position: Int) {
adapter.isAppendData = true//设置数据状态为追加数据
viewModel.fetchMore()
}
})
}
findViewById<RecyclerView>(R.id.recyclerview).apply {
val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
this.layoutManager = layoutManager
this.adapter = adapter
}
supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true)
it.setHomeAsUpIndicator(R.drawable.ic_baseline_menu_24)
}
//侧边栏
val sp = getSharedPreferences("username", 0)
username = sp.getString("username", null)
}
override fun onResume() {
super.onResume()
//加载源改变
val configSp = getSharedPreferences("com.lsp.view_preferences", 0)
val nowSourceName = configSp.getString("source_name", null)
if (nowSourceName != null && viewModel.uiState.value.nowSourceName.value != nowSourceName){
viewModel.updateNowSource(nowSourceName)
}
val nowMode = configSp.getBoolean("safe_mode", true)
if (viewModel.uiState.value.isSafe != nowMode){
viewModel.updateSafeMode(nowMode)
Log.e(TAG,nowMode.toString())
}
}
}
@@ -1,62 +0,0 @@
package com.lsp.view.activity.pic
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
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.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) {
val tagText = view.findViewById<TextView>(R.id.tag)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.tag_item_layout, parent, false)
val viewHolder = ViewHolder(view)
viewHolder.tagText.setOnLongClickListener {
val position = viewHolder.adapterPosition
if (position == 0) {
//标题
return@setOnLongClickListener false
}
var tagsArray: ArrayList<Tags> = ArrayList()
val tagsArraySp = context.getSharedPreferences("tags_sp", Context.MODE_PRIVATE)
val tagsArrayJson = tagsArraySp.getString("array", null)
if (tagsArrayJson != null) {
val tagListType = object : TypeToken<ArrayList<Tags>>() {}.type
tagsArray = Gson().fromJson(tagsArrayJson, tagListType)
}
for (tag: Tags in tagsArray) {
if (tag.tag == viewHolder.tagText.text.toString()) {
Snackbar.make(viewHolder.tagText, "收藏过了哦", Snackbar.LENGTH_SHORT).show()
return@setOnLongClickListener true
}
}
tagsArray.add(Tags(viewHolder.tagText.text.toString()))
tagsArraySp.edit().putString("array", Gson().toJson(tagsArray)).apply()
Snackbar.make(viewHolder.tagText, "收藏了新标签", Snackbar.LENGTH_SHORT).show()
return@setOnLongClickListener true
}
return viewHolder
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tag = idList[position]
holder.tagText.text = tag.id
}
override fun getItemCount(): Int {
return idList.size
}
}
@@ -1,297 +0,0 @@
package com.lsp.view.activity.pic
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.*
import android.view.KeyEvent
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.activity.addCallback
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.chrisbanes.photoview.PhotoView
import com.google.android.flexbox.*
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.lsp.view.R
import com.lsp.view.activity.main.MainActivity
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
class PicActivity : AppCompatActivity() {
private val idList = ArrayList<ID>()
private val sizeList = ArrayList<Size>()
private lateinit var image: ImageView
private lateinit var photoView: PhotoView
private var shortAnnotationDuration by Delegates.notNull<Int>()
private var md5 : String?= null
companion object{
fun actionStartActivity(context: Context,id:String,sample_url:String,file_url:String,tags:String,
file_ext:String,file_size:String,md5:String,sample_height:Int,sample_width:Int){
val intent=Intent(context,PicActivity::class.java)
intent.putExtra("id", id)
intent.putExtra("sample_url", sample_url)
intent.putExtra("file_url", file_url)
intent.putExtra("tags", tags)
intent.putExtra("file_ext", file_ext)
intent.putExtra("file_size", file_size)
intent.putExtra("md5", md5)
intent.putExtra("sample_height",sample_height)
intent.putExtra("sample_width",sample_width)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pic)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
image = findViewById(R.id.titleImage)
val sample_height = intent.getIntExtra("sample_height",-1)
val sample_width = intent.getIntExtra("sample_width",-1)
if (sample_width > sample_height){
image.scaleType = ImageView.ScaleType.CENTER_INSIDE
}
shortAnnotationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
val intent = intent
val tags = intent.getStringExtra("tags")
if (tags != null) {
loadTags(tags, "tags")
}
val file_size = intent.getStringExtra("file_size")
if (file_size != null) {
loadTags(file_size, "size")
}
md5 = intent.getStringExtra("md5")
val id = intent.getStringExtra("id")
if (id != null) {
loadTags(id, "id")
}
val sample_url = intent.getStringExtra("sample_url")
sample_url?.let {
loadPic(it)
}
val file_url = intent.getStringExtra("file_url")
val file_ext = intent.getStringExtra("file_ext")
val download =
findViewById<FloatingActionButton>(
R.id.download
)
photoView = findViewById(R.id.photoView)
image.setOnClickListener {
photoView.apply {
alpha = 0f
visibility = View.VISIBLE
animate()
.alpha(1f)
.setDuration(shortAnnotationDuration.toLong())
.setListener(null)
}
Glide.with(this).load(sample_url).into(photoView)
}
photoView.setOnClickListener {
photoView.animate()
.alpha(0f)
.setDuration(shortAnnotationDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
photoView.visibility = View.GONE
}
})
}
val share = findViewById<FloatingActionButton>(R.id.share)
share.setOnClickListener {
if (sample_url != null) {
Util.share(sample_url,this)
}
}
download.setOnClickListener {
Util.download(file_url, file_ext, md5)
}
val f_btn = findViewById<FloatingActionButton>(R.id.float_btn)
val ctrl = findViewById<LinearLayout>(R.id.ctrl)
f_btn.setOnClickListener {
if (ctrl.visibility == View.VISIBLE)
ctrl.visibility = View.GONE
else
ctrl.visibility = View.VISIBLE
}
val back = findViewById<ImageView>(R.id.back)
back.setOnClickListener {
finish()
}
}
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
onBackPressedDispatcher.addCallback{
if (photoView.visibility != View.GONE) {
photoView.animate()
.alpha(0f)
.setDuration(shortAnnotationDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
photoView.visibility = View.GONE
}
})
}else{
finish()
}
}
return super.dispatchKeyEvent(event)
}
private fun loadPic(url: String) {
val glideUrl = GlideUrl(
url,
LazyHeaders.Builder().addHeader(
"User-Agent",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36"
).build()
)
Glide.with(this).load(glideUrl).listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>?,
isFirstResource: Boolean
): Boolean {
e?.printStackTrace()
val back = findViewById<ImageView>(R.id.back)
Snackbar.make(back, R.string.toast_load_fail, Snackbar.LENGTH_LONG).setAction(R.string.button_check) {
AlertDialog.Builder(this@PicActivity).apply {
setTitle("Log")
if (e != null) {
setMessage(e.stackTraceToString())
}
setNegativeButton(R.string.button_ok, null)
create()
show()
}
}.show()
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
return false
}
}).into(image)
}
private fun layoutManager(): FlexboxLayoutManager {
val manager = object : FlexboxLayoutManager(this, FlexDirection.ROW, FlexWrap.WRAP) {
override fun canScrollVertically(): Boolean {
return false
}
}
manager.alignItems = AlignItems.CENTER
manager.justifyContent = JustifyContent.FLEX_START
return manager
}
//设置tags列表
//这写的很烂 得改
private fun loadTags(tags: String, type: String) {
val tagList = ArrayList<Tags>()
when (type) {
"tags" -> {
tagList.add(Tags("Tag"))
val list = tags.split(" ")
val tagRecyclerView =
findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.tagRecyclerView)
tagRecyclerView.layoutManager = layoutManager()
for (tag in list) run {
tagList.add(Tags(tag))
}
val adapter = TagAdapter(tagList, this)
adapter.setOnItemClickListener(object : TagAdapter.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
val intent = Intent(this@PicActivity, MainActivity::class.java)
intent.putExtra("searchTag", tagList[position].tag)
startActivity(intent)
}
})
tagRecyclerView.adapter = adapter
}
"id" -> {
idList.add(ID("ID"))
idList.add(ID(tags))
val idRecyclerView =
findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.idRecyclerView)
idRecyclerView.layoutManager = layoutManager()
val adapter = IdAdapter(idList,this)
idRecyclerView.adapter = adapter
}
"size" -> {
sizeList.add(Size("Size"))
sizeList.add(Size(tags))
val sizeRecyclerView =
findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.sizeRecyclerView)
sizeRecyclerView.layoutManager = layoutManager()
val adapter = SizeAdapter(sizeList)
sizeRecyclerView.adapter = adapter
}
}
}
}
@@ -1,40 +0,0 @@
package com.lsp.view.activity.pic
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.lsp.view.R
import com.lsp.view.repository.bean.Size
import java.math.RoundingMode
import java.text.DecimalFormat
class SizeAdapter(val tagList: List<Size>) : RecyclerView.Adapter<SizeAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tagText = view.findViewById<TextView>(R.id.tag)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.tag_item_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tag = tagList[position]
if (position == 0) {
holder.tagText.text = tag.file_size
} else {
val sizeKb = tag.file_size.toFloat() / 1024
val format = DecimalFormat("0.##")
format.roundingMode = RoundingMode.FLOOR
val size = format.format(sizeKb)
holder.tagText.text = "$size KB"
}
}
override fun getItemCount(): Int {
return tagList.size
}
}
@@ -1,76 +0,0 @@
package com.lsp.view.activity.pic
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
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.repository.bean.Tags
class TagAdapter(val tagList: List<Tags>, val context: Context) :
RecyclerView.Adapter<TagAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tagText = view.findViewById<TextView>(R.id.tag)
}
private lateinit var mOnItemClickListener: OnItemClickListener
interface OnItemClickListener {
fun onItemClick(view: View, position: Int)
}
fun setOnItemClickListener(mOnItemClickListener: OnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.tag_item_layout, parent, false)
val viewHolder = ViewHolder(view)
viewHolder.tagText.setOnLongClickListener {
val position = viewHolder.adapterPosition
if (position == 0) {
//标题
return@setOnLongClickListener false
}
var tagsArray: ArrayList<Tags> = ArrayList()
val tagsArraySp = context.getSharedPreferences("tags_sp", Context.MODE_PRIVATE)
val tagsArrayJson = tagsArraySp.getString("array", null)
if (tagsArrayJson != null) {
val tagListType = object : TypeToken<ArrayList<Tags>>() {}.type
tagsArray = Gson().fromJson(tagsArrayJson, tagListType)
}
for (tag: Tags in tagsArray) {
if (tag.tag == viewHolder.tagText.text.toString()) {
Snackbar.make(viewHolder.tagText, R.string.toast_tag_exist, Snackbar.LENGTH_SHORT).show()
return@setOnLongClickListener true
}
}
tagsArray.add(Tags(viewHolder.tagText.text.toString()))
tagsArraySp.edit().putString("array", Gson().toJson(tagsArray)).apply()
Snackbar.make(viewHolder.tagText, R.string.toast_tag_add_fav, Snackbar.LENGTH_SHORT).show()
return@setOnLongClickListener true
}
return viewHolder
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tag = tagList[position]
holder.tagText.text = tag.tag
holder.tagText.setOnClickListener {
mOnItemClickListener.onItemClick(it, position)
}
}
override fun getItemCount(): Int {
return tagList.size
}
}
@@ -0,0 +1,49 @@
package com.lsp.view.bean
import android.os.Parcel
import android.os.Parcelable
open class Post(
open val postId: String,
open val rating: String,
open val sampleUrl:String,
open val fileUrl:String,
open val sampleHeight: Int,
open val sampleWidth: Int,
): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readInt(),
parcel.readInt()
)
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(postId)
dest.writeString(rating)
dest.writeString(sampleUrl)
dest.writeString(fileUrl)
dest.writeInt(sampleHeight)
dest.writeInt(sampleWidth)
}
companion object {
@JvmField
val CREATOR = object:Parcelable.Creator<Post> {
override fun createFromParcel(parcel: Parcel): Post {
return Post(parcel)
}
override fun newArray(size: Int): Array<Post?> {
return arrayOfNulls(size)
}
}
}
}
@@ -0,0 +1,14 @@
package com.lsp.view.bean
import com.google.gson.annotations.SerializedName
class YandPost(
@SerializedName("file_url") override val fileUrl: String,
@SerializedName("sample_url") override val sampleUrl: String,
@SerializedName("sample_height") override val sampleHeight : Int,
@SerializedName("sample_width") override val sampleWidth: Int,
@SerializedName("file_ext") val fileExt: String,
@SerializedName("file_size") val fileSize: String,
@SerializedName("id") override val postId: String,
rating: String
):Post(postId, rating, sampleUrl,fileUrl,sampleHeight,sampleWidth)
@@ -0,0 +1,88 @@
package com.lsp.view.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.lsp.view.fragment.adapter.PostAdapter
import com.lsp.view.R
import com.lsp.view.activity.MainActivity
import com.lsp.view.bean.Post
/**
* A simple [Fragment] subclass.
* create an instance of this fragment.
*/
class CollectFragment : Fragment() {
private lateinit var activityContext: MainActivity
private val collectAdapter by lazy {
PostAdapter(activityContext)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityContext = requireActivity() as MainActivity
activityContext.viewModel.getCollectList()
activityContext.viewModel.collectList.observe(this){
collectAdapter.pushNewData(it)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_collect, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//刷新
val refresh =
view.findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(R.id.swipeRefreshLayout)
activityContext.viewModel.uiState.value.isRefreshing.observe(activityContext) {
refresh.isRefreshing = it
}
collectAdapter.setOnListItemClick(object : PostAdapter.OnListItemClick {
override fun setOnListItemClick(post: Post) {
val bundle = Bundle()
bundle.putParcelable("post",post)
Navigation.findNavController(view).navigate(R.id.action_collectFragment_to_imageFragment,bundle)
val slideOutAnimation = AnimationUtils.loadAnimation(context,
R.anim.slide_out_bottom
)
activityContext.bottomNav.startAnimation(slideOutAnimation)
activityContext.bottomNav.visibility = View.GONE
}
})
view.findViewById<RecyclerView>(R.id.recyclerview).apply {
val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
this.layoutManager = layoutManager
this.adapter = collectAdapter
}
}
override fun onResume() {
super.onResume()
if (activityContext.bottomNav.visibility == View.GONE){
val slideOutAnimation = AnimationUtils.loadAnimation(context, R.anim.slide_on_bottom)
activityContext.bottomNav.startAnimation(slideOutAnimation)
activityContext.bottomNav.visibility = View.VISIBLE
}
}
}
@@ -0,0 +1,206 @@
package com.lsp.view.fragment
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
import android.os.Environment
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.lsp.view.R
import com.lsp.view.YandViewApplication
import com.lsp.view.activity.MainActivity
import com.lsp.view.bean.Post
import com.lsp.view.repository.datasource.model.Collect
import com.lsp.view.repository.datasource.model.Collect.Companion.toCollect
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class ImageFragment : Fragment() {
private val post by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelable("post",Post::class.java)
}else{
arguments?.getParcelable("post")
}
}
@Volatile private var isCollect = false
private val activityContext : MainActivity by lazy {
requireActivity() as MainActivity
}
private lateinit var collectBtn:MenuItem
private lateinit var collect:Collect
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_image, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bottomAppBar = view.findViewById<BottomAppBar>(R.id.bottomAppBar)
collectBtn = bottomAppBar.menu.findItem(R.id.collect_menu_btn)
collectBtn.setIcon(R.drawable.ic_bashline_unfavorite_24)
bottomAppBar.setOnMenuItemClickListener {
when(it.itemId){
R.id.share_menu_btn ->{
post?.sampleUrl?.let { it1 -> share(it1,activityContext) }
true
}
R.id.collect_menu_btn ->{
if (!isCollect){
collect = post!!.toCollect()
collectIt(collect)
}else{
unCollectIt(collect)
}
true
}
else -> {false}
}
}
lifecycleScope.launch {
post?.postId?.let {
val collectPost = activityContext.viewModel.getCollectByPostId(it)
val result = collectPost.await()
if (result != null){
collect = result
collectBtn.setIcon(R.drawable.ic_baseline_favorite_24)
isCollect = true
}
}
}
val downloadBtn = view.findViewById<FloatingActionButton>(R.id.download_btn)
downloadBtn.setOnClickListener {
lifecycleScope.launch {
if (post!=null){
download(post!!.fileUrl)
}
}
}
if (post!=null){
val imageContent = view.findViewById<ImageView>(R.id.image_content)
val glideUrl = GlideUrl(
post!!.sampleUrl,
LazyHeaders.Builder().addHeader("User-Agent", YandViewApplication.UA)
.build()
)
Glide.with(this).load(glideUrl).into(imageContent)
}
}
private fun collectIt(collect: Collect){
activityContext.viewModel.addCollect(collect)
collectBtn.setIcon(R.drawable.ic_baseline_favorite_24)
isCollect = true
}
private fun unCollectIt(collect:Collect){
activityContext.viewModel.removeCollect(collect)
collectBtn.setIcon(R.drawable.ic_bashline_unfavorite_24)
isCollect = false
}
private fun download(fileUrl: String){
lifecycleScope.launch(Dispatchers.IO) {
activityContext.viewModel.postNewToast("Start download")
val result = (activityContext.application as YandViewApplication).downloadBinder.downloadImage(fileUrl)
if (result.isSuccess){
activityContext.viewModel.postNewToast("Download successfully")
}else{
val exception = result.exceptionOrNull()
activityContext.viewModel.postNewToast(exception?.message.toString())
}
}
}
private fun share(url:String,context: Context){
lifecycleScope.launch(Dispatchers.IO) {
val bitmap = Glide.with(context).asBitmap().load(url).submit().get()
val path = File("${context.cacheDir}/image")
val split = url.split("/")
val fileName = split[split.size - 1]
if (!path.exists())
path.mkdirs()
var file = File("${context.cacheDir}/image/$fileName")
val downloadFile =
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$fileName")
if (downloadFile.exists()){
file = downloadFile
}else if (!file.exists()) {
file.outputStream().apply {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, this)
}
}
val imageUri = FileProvider.getUriForFile(
context,
"com.lsp.view.fileprovider",
file
)
context.grantUriPermission(
"com.lsp.view",
imageUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "image/*"
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri)
shareIntent.putExtra(Intent.EXTRA_TITLE,fileName)
val intent = Intent.createChooser(shareIntent, R.string.title_share.toString())
val resInfoList: List<ResolveInfo> = if (Build.VERSION.SDK_INT < 33) {
context.packageManager
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
} else {
context.packageManager.queryIntentActivities(
intent,
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
)
}
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
context.grantUriPermission(
packageName,
imageUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
context.startActivity(intent)
}
}
}
@@ -0,0 +1,113 @@
package com.lsp.view.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.view.inputmethod.EditorInfo
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import com.lsp.view.fragment.adapter.PostAdapter
import com.lsp.view.R
import com.lsp.view.activity.MainActivity
import com.lsp.view.bean.Post
import com.lsp.view.model.MainViewModel
class PostListFragment:Fragment() {
private val viewModel: MainViewModel by lazy {
ViewModelProvider(requireActivity(),MainViewModel.provideFactory(activityContext.repository,requireContext(),this)).get(MainViewModel::class.java)
}
private lateinit var activityContext: MainActivity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activityContext = requireActivity() as MainActivity
val searchBar = view.findViewById<SearchBar>(R.id.search_bar)
val searchView = view.findViewById<SearchView>(R.id.search_view)
searchView.editText.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
viewModel.fetchPostBySearch(searchView.text.toString())
searchBar.setText(searchView.text)
searchView.hide()
}
return@setOnEditorActionListener false
}
viewModel.uiState.value.nowSearchText.observe(requireActivity()){
searchView.setText(it)
searchBar.setText(it)
}
//刷新
val refresh =
view.findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(R.id.swipeRefreshLayout)
viewModel.uiState.value.isRefreshing.observe(activityContext) {
refresh.isRefreshing = it
}
refresh.setOnRefreshListener {
viewModel.fetchPostByRefresh()
}
viewModel.postList.observe(activityContext){
viewModel.adapter.pushNewData(it)
}
viewModel.adapter.apply {
setLoadMoreListener(object : PostAdapter.OnScrollToBottom {
override fun event(position: Int) {
viewModel.fetchMore()
}
})
}
view.findViewById<RecyclerView>(R.id.recyclerview).apply {
val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
this.layoutManager = layoutManager
this.adapter = viewModel.adapter
}
viewModel.adapter.setOnListItemClick(object : PostAdapter.OnListItemClick {
override fun setOnListItemClick(post: Post) {
val bundle = Bundle()
bundle.putParcelable("post",post)
Navigation.findNavController(view).navigate(R.id.action_postListFragment_to_imageFragment,bundle)
val slideOutAnimation = AnimationUtils.loadAnimation(context,
R.anim.slide_out_bottom
)
activityContext.bottomNav.startAnimation(slideOutAnimation)
activityContext.bottomNav.visibility = View.GONE
}
})
}
override fun onResume() {
super.onResume()
if (activityContext.bottomNav.visibility == View.GONE){
val slideOutAnimation = AnimationUtils.loadAnimation(context, R.anim.slide_on_bottom)
activityContext.bottomNav.startAnimation(slideOutAnimation)
activityContext.bottomNav.visibility = View.VISIBLE
}
}
}
@@ -1,4 +1,4 @@
package com.lsp.view.activity.main
package com.lsp.view.fragment.adapter
import android.content.Context
import android.view.LayoutInflater
@@ -10,34 +10,51 @@ 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.repository.bean.YandPost
import com.lsp.view.YandViewApplication
import com.lsp.view.bean.Post
class PostAdapter(val context: Context,private val postList: ArrayList<YandPost> = ArrayList()):
class PostAdapter(val context: Context,private val postList: ArrayList<Post> = ArrayList()):
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"
var isAppendData = false//数据追加标志
private var inBottom:Boolean = false//当前列表是否在底部
@Volatile private var isProcess:Boolean = false
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val picImage: ImageView = view.findViewById(R.id.picImgae)
val picImage: ImageView = view.findViewById(R.id.image)
}
fun pushNewData(list: List<YandPost>) {
val oldSize = postList.size
postList.clear()
notifyItemRangeRemoved(0,oldSize)
postList.addAll(list)
notifyItemRangeInserted(0, list.size)
fun pushNewData(list: List<Post>) {
if (!isProcess) {
isProcess = true
if (inBottom) {
//在底部,则追加数据
val pos = postList.size
postList.addAll(list)
notifyItemRangeInserted(pos - 1, list.size)
} else {
//不在则刷新数据
val oldSize = postList.size
postList.clear()
notifyItemRangeRemoved(0, oldSize)
postList.addAll(list)
notifyItemRangeInserted(0, list.size)
}
}
isProcess = false
inBottom = false//加入新数据后不在底部,若仍在底部说明无更多数据,也不必处理
}
fun appendDate(list: List<YandPost>){
val pos = postList.size
postList.addAll(list)
notifyItemRangeInserted(pos-1, list.size)
isAppendData = false
private lateinit var mOnListItemClick: OnListItemClick
interface OnListItemClick {
fun setOnListItemClick(post:Post)
}
fun setOnListItemClick(mOnListItemClick: OnListItemClick) {
this.mOnListItemClick = mOnListItemClick
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -46,27 +63,7 @@ class PostAdapter(val context: Context,private val postList: ArrayList<YandPost>
viewHolder.picImage.setOnClickListener {
val position = viewHolder.adapterPosition
// 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 = postList[position].sample_url.split(".")
file_ext = strarr[strarr.lastIndex]
}
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)
mOnListItemClick.setOnListItemClick(postList[position])
}
@@ -93,10 +90,10 @@ class PostAdapter(val context: Context,private val postList: ArrayList<YandPost>
}
for(index in p..last){
if (p>6) {
val source: String = postList[index].sample_url
val source: String = postList[index].sampleUrl
val glideUrl = GlideUrl(
source,
LazyHeaders.Builder().addHeader("User-Agent", UA)
LazyHeaders.Builder().addHeader("User-Agent", YandViewApplication.UA)
.build()
)
Glide.with(context).download(glideUrl).preload()
@@ -112,10 +109,10 @@ class PostAdapter(val context: Context,private val postList: ArrayList<YandPost>
val post = postList[position]
val source: String = post.sample_url
val source: String = post.sampleUrl
val height = postList[position].sample_height
val width = postList[position].sample_width
val height = postList[position].sampleHeight
val width = postList[position].sampleWidth
//图片过长则缩减高度以保证显示完整
if (width > height){
@@ -127,22 +124,24 @@ class PostAdapter(val context: Context,private val postList: ArrayList<YandPost>
val glideUrl = GlideUrl(
source,
LazyHeaders.Builder().addHeader("User-Agent", UA)
LazyHeaders.Builder().addHeader("User-Agent", YandViewApplication.UA)
.build()
)
Glide.with(context).load(glideUrl).into(holder.picImage)
if (position == postList.size - 1 && postList.size > 6) {
if (position == postList.size - 1 && postList.size > 6 ) {
//到达底部
mScrollToBottom.event(position)
inBottom = true
if (this::mScrollToBottom.isInitialized) {
mScrollToBottom.event(position)
}
}
}
override fun getItemCount(): Int {
val size = postList.size
return size
return postList.size
}
@@ -8,33 +8,51 @@ import androidx.lifecycle.MutableLiveData
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 com.lsp.view.fragment.adapter.PostAdapter
import com.lsp.view.repository.network.PostRepository
import androidx.savedstate.SavedStateRegistryOwner
import com.lsp.view.repository.bean.Post
import com.lsp.view.repository.bean.YandPost
import com.lsp.view.YandViewApplication
import com.lsp.view.bean.YandPost
import com.lsp.view.repository.datasource.CollectDatabase
import com.lsp.view.repository.datasource.CollectRepository
import com.lsp.view.repository.datasource.CollectRepositoryImpl
import com.lsp.view.repository.datasource.model.Collect
import com.lsp.view.repository.exception.NetworkErrorException
import com.lsp.view.ui.UiState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
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,context: Context):ViewModel() {
class MainViewModel(private val repository: PostRepository, context: Context):ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState:StateFlow<UiState>
get() = _uiState.asStateFlow()
private var fetchJob: Job? = null
private val TAG = this::class.java.simpleName
private val _errorMessage = MutableLiveData<String>()
val errorMessage
get() = _errorMessage as LiveData<String>
private val _toastMessage = MutableLiveData<String>()
val toastMessage
get() = _toastMessage as LiveData<String>
private val _postList = MutableLiveData<ArrayList<YandPost>>()
val postList get() = _postList as LiveData<ArrayList<YandPost>>
val adapter by lazy {
fetchPostByRefresh()
PostAdapter(context)
}
private val _collectList:MutableLiveData<List<Collect>> = MutableLiveData()
val collectList:LiveData<List<Collect>>
get() = _collectList
private val collectRepository: CollectRepository by lazy {
CollectRepositoryImpl(CollectDatabase.getDatabase(context).collectDao())
}
@Volatile private var isProcessing = false
companion object {
fun provideFactory(
@@ -76,7 +94,7 @@ class MainViewModel(private val repository: PostRepository,context: Context):Vie
}
private fun fetchNewPost(append:Boolean = false){
private fun fetchNewPost(){
fetchJob?.cancel()
fetchJob = viewModelScope.launch(Dispatchers.IO) {
_uiState.value.isRefreshing.postValue(true)
@@ -88,7 +106,7 @@ class MainViewModel(private val repository: PostRepository,context: Context):Vie
))
}catch (e:NetworkErrorException){
_errorMessage.postValue(e.message.toString())
postNewToast(e.message.toString())
}
_uiState.value.isRefreshing.postValue(false)
@@ -118,7 +136,7 @@ class MainViewModel(private val repository: PostRepository,context: Context):Vie
fun fetchMore(){
_uiState.value.nowPage++
fetchNewPost(true)
fetchNewPost()
}
fun updateSafeMode(mode:Boolean){
@@ -132,4 +150,40 @@ class MainViewModel(private val repository: PostRepository,context: Context):Vie
_uiState.value.nowSourceName.postValue(source)
fetchPostByRefresh()
}
fun getCollectList(){
viewModelScope.launch {
_collectList.postValue(collectRepository.getAllCollect())
}
}
fun addCollect(collect: Collect){
if (!isProcessing){
viewModelScope.launch {
collectRepository.addNewCollect(collect)
}
}
}
fun removeCollect(collect:Collect){
if (!isProcessing){
viewModelScope.launch {
collectRepository.removeCollect(collect)
}
}
}
suspend fun getCollectByPostId(postId:String) = viewModelScope.async {
val type = YandViewApplication.context?.getSharedPreferences("com.lsp.view_preferences",Context.MODE_PRIVATE)?.getString("source_name", "yande.re")
collectRepository.getCollectByPostId(postId,type!!)
}
fun postNewToast(content:String){
_toastMessage.postValue(content)
}
}
@@ -1,3 +0,0 @@
package com.lsp.view.repository.bean
class Author(val author: String)
@@ -1,3 +0,0 @@
package com.lsp.view.repository.bean
class ID(val id: String)
@@ -1,7 +0,0 @@
package com.lsp.view.repository.bean
open class Post(
val id: String,
val rating: String,
val author: String,
)
@@ -1,3 +0,0 @@
package com.lsp.view.repository.bean
class Size(val file_size: String)
@@ -1,3 +0,0 @@
package com.lsp.view.repository.bean
class Tags(val tag: String)
@@ -1,16 +0,0 @@
package com.lsp.view.repository.bean
class YandPost(
val preview_url: String,
val file_url: String,
val sample_url: String,
val tags: String,
val file_ext: String,
val file_size: String,
val sample_height : Int,
val sample_width: Int,
id: String,
rating: String,
author: String,
val md5 : String
):Post(id, rating, author)
@@ -0,0 +1,24 @@
package com.lsp.view.repository.datasource
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.lsp.view.repository.datasource.dao.CollectDao
import com.lsp.view.repository.datasource.model.Collect
@Database(entities = [Collect::class], version = 1,exportSchema = false)
abstract class CollectDatabase: RoomDatabase(){
abstract fun collectDao():CollectDao
companion object{
@Volatile
private var Instance:CollectDatabase? = null
fun getDatabase(context: Context):CollectDatabase{
return Instance?: synchronized(this) {
Room.databaseBuilder(context,CollectDatabase::class.java,"collect_database").build().also { Instance = it }
}
}
}
}
@@ -0,0 +1,12 @@
package com.lsp.view.repository.datasource
import com.lsp.view.repository.datasource.model.Collect
interface CollectRepository {
suspend fun addNewCollect(collect: Collect)
suspend fun getAllCollect():List<Collect>
suspend fun getCollectByPostId(postId:String,type:String):Collect?
suspend fun removeCollect(collect: Collect)
}
@@ -0,0 +1,23 @@
package com.lsp.view.repository.datasource
import com.lsp.view.repository.datasource.dao.CollectDao
import com.lsp.view.repository.datasource.model.Collect
class CollectRepositoryImpl(private val collectDao:CollectDao):CollectRepository {
override suspend fun addNewCollect(collect: Collect) {
collectDao.insert(collect)
}
override suspend fun getAllCollect(): List<Collect> {
return collectDao.getAllCollect()
}
override suspend fun getCollectByPostId(postId: String,type:String): Collect? {
return collectDao.getCollectByPostId(postId,type)
}
override suspend fun removeCollect(collect: Collect) {
collectDao.delete(collect.postId,collect.type)
}
}
@@ -0,0 +1,19 @@
package com.lsp.view.repository.datasource.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.lsp.view.repository.datasource.model.Collect
@Dao
interface CollectDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(collect:Collect)
@Query("select * from collect")
suspend fun getAllCollect():List<Collect>
@Query("delete from collect where post_id = :postId and type = :type")
suspend fun delete(postId: String,type: String)
@Query("select * from collect where post_id = :postId and type = :type")
suspend fun getCollectByPostId(postId:String,type:String):Collect?
}
@@ -0,0 +1,35 @@
package com.lsp.view.repository.datasource.model
import android.content.Context
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.lsp.view.YandViewApplication
import com.lsp.view.bean.Post
@Entity(tableName = "collect")
data class Collect(
@PrimaryKey(autoGenerate = true) var id:Int = 0,
@ColumnInfo(name = "post_id")override val postId: String,
@ColumnInfo(name = "sample_url")override val sampleUrl:String,
@ColumnInfo(name = "rating")override val rating:String,
@ColumnInfo(name = "file_url")override val fileUrl:String,
@ColumnInfo(name = "sampleHeight") override val sampleHeight:Int,
@ColumnInfo(name = "sampleWeight") override val sampleWidth:Int,
@ColumnInfo(name = "type")val type:String
):Post(postId,rating,sampleUrl,fileUrl,sampleHeight,sampleWidth){
constructor(
postId: String,
sampleUrl:String,
rating:String,
fileUrl:String,
sampleHeight:Int,
sampleWeight:Int,
): this(0, postId, sampleUrl, rating, fileUrl, sampleHeight, sampleWeight,
YandViewApplication.context?.getSharedPreferences("com.lsp.view_preferences",Context.MODE_PRIVATE)?.getString("source_name", "yande.re")!!)
companion object{
fun Post.toCollect():Collect = Collect(this.postId,this.sampleUrl,this.rating,this.fileUrl,this.sampleHeight,this.sampleWidth )
}
}
@@ -1,11 +1,11 @@
package com.lsp.view.repository
package com.lsp.view.repository.network
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
import com.lsp.view.repository.network.api.PostApi
import com.lsp.view.repository.network.api.PostApiImpl
import com.lsp.view.bean.YandPost
class PostDataSource {
private val api: PostApi = PostApiImpl()
@@ -25,7 +25,7 @@ data class Load(
lateinit var source:String
companion object{
fun Builder(tags: String?, page: Int, safe:Boolean):Load{
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)
@@ -1,6 +1,6 @@
package com.lsp.view.repository
package com.lsp.view.repository.network
import com.lsp.view.repository.bean.YandPost
import com.lsp.view.bean.YandPost
class PostRepository {
@@ -1,8 +1,8 @@
package com.lsp.view.repository.api
package com.lsp.view.repository.network.api
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.network.Load
import com.lsp.view.bean.Post
import com.lsp.view.bean.YandPost
import com.lsp.view.repository.exception.NetworkErrorException
import retrofit2.Call
@@ -35,5 +35,5 @@ interface PostApi {
}
}
suspend fun fetchNewPost(load:Load):ArrayList<YandPost>
suspend fun fetchNewPost(load: Load):ArrayList<YandPost>
}
@@ -1,9 +1,9 @@
package com.lsp.view.repository.api
package com.lsp.view.repository.network.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 com.lsp.view.repository.network.Load
import com.lsp.view.repository.network.retrofit.PostService
import com.lsp.view.repository.network.retrofit.ServiceCreator
import com.lsp.view.bean.YandPost
import retrofit2.Call
import kotlin.collections.ArrayList
@@ -1,6 +1,6 @@
package com.lsp.view.retrofit
package com.lsp.view.repository.network.retrofit
import com.lsp.view.repository.bean.YandPost
import com.lsp.view.bean.YandPost
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
@@ -1,4 +1,4 @@
package com.lsp.view.retrofit
package com.lsp.view.repository.network.retrofit
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
@@ -1,146 +1,75 @@
package com.lsp.view.service
import android.accounts.NetworkErrorException
import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.MediaScannerConnection
import android.os.*
import com.lsp.view.util.Util
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.security.MessageDigest
import kotlin.concurrent.thread
import java.io.Serializable
class DownloadService : Service() {
private val mBinder = DownloadBinder(this)
class DownloadBinder(val context: Context) : Binder() {
fun callBack(handler: Handler,status:Int){
val msg = Message.obtain()
msg.obj = status
handler.sendMessage(msg)
fun downloadImage(fileUrl: String):Result<Serializable> {
}
fun downloadPic(file_url: String, end: String,handler: Handler,md5 : String?) {
thread {
val FileD =
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/")
if (FileD.exists()) {
val file =
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$md5.$end")
if (file.exists()){
callBack(handler,Util.FILE_EXISTS)
return@thread
}
val fos = FileOutputStream(file)
try {
val client = OkHttpClient()
val request = Request.Builder()
.url(file_url)
.build()
val response = client.newCall(request).execute()
val responseData = response.body()?.bytes()
if (response.code() == 200) {
fos.write(responseData)
//通知媒体更新
MediaScannerConnection.scanFile(
context, arrayOf(file.path),
null, null
)
if (md5 == getFileMD5(file.path)) {
callBack(handler, Util.DOWNLOAD_SUCCESS)
} else {
file.delete()
callBack(handler,Util.MD5_COMPARE_ERROR)
}
return@thread
} else {
file.delete()
callBack(handler, Util.NETWORK_ERROR)
return@thread
}
} catch (e: Exception) {
e.printStackTrace()
callBack(handler, Util.DOWNLOAD_ERROR)
return@thread
} finally {
fos.close()
}
} else {
FileD.mkdirs()
downloadPic(file_url, end, handler, md5)
}
callBack(handler,Util.DOWNLOAD_ERROR)
val fileDir =
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/")
if (!fileDir.exists()) {
fileDir.mkdirs()
}
}
/**
* 获取文件MD5
*/
fun getFileMD5(path: String?): String? {
if (path.isNullOrEmpty()) {
return null
val split = fileUrl.split("/")
val fileName = split[split.size - 1]
val file =
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$fileName")
if (file.exists()) {
return Result.failure<Exception>(Exception("File exists"))
}
var digest: MessageDigest? = null
var fileIS: FileInputStream? = null
val buffer = ByteArray(1024)
var len = 0
val fos = FileOutputStream(file)
try {
digest = MessageDigest.getInstance("MD5")
val oldF = File(path)
fileIS = FileInputStream(oldF)
while (fileIS.read(buffer).also { len = it } != -1) {
digest.update(buffer, 0, len)
val client = OkHttpClient()
val request = Request.Builder()
.url(fileUrl)
.build()
val response = client.newCall(request).execute()
val responseData = response.body()?.bytes()
return if (response.code() == 200) {
fos.write(responseData)
//通知媒体更新
MediaScannerConnection.scanFile(
context, arrayOf(file.path),
null, null
)
Result.success("File download successfully")
} else {
file.delete()
Result.failure<Exception>(NetworkErrorException("Connection timed out"))
}
} catch (e: Exception) {
e.printStackTrace()
return null
}finally {
fileIS?.close()
}
return bytesToHexString(digest?.digest())
}
fun bytesToHexString(src: ByteArray?): String? {
val result = StringBuilder("")
if (src?.isEmpty()==true) {
return null
Result.failure<Exception>(e)
} finally {
fos.close()
}
src?.forEach {
var i = it.toInt()
//这里需要对b与0xff做位与运算,
//若b为负数,强制转换将高位位扩展,导致错误,
//故需要高位清零
val hexStr = Integer.toHexString(i and 0xff)
//若转换后的十六进制数字只有一位,
//则在前补"0"
if (hexStr.length == 1) {
result.append(0)
}
result.append(hexStr)
}
return result.toString()
return Result.failure(Exception("Unknown error"))
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
}
}
@@ -1,4 +1,4 @@
package com.lsp.view.model
package com.lsp.view.ui
import androidx.lifecycle.MutableLiveData
@@ -9,8 +9,4 @@ data class UiState(
val nowSourceName: MutableLiveData<String> = MutableLiveData(),
val isSafe: Boolean = true, //安全模式
var nowPage:Int = 1,
){
fun switchShowSearchBar(){
showSearchBar = !showSearchBar
}
}
)
-114
View File
@@ -1,114 +0,0 @@
package com.lsp.view.util
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.graphics.Bitmap
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.widget.Toast
import androidx.core.content.FileProvider
import com.bumptech.glide.Glide
import com.lsp.view.R
import com.lsp.view.YandViewApplication
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()
val path = File("${context.cacheDir}/image")
if (!path.exists())
path.mkdirs()
val file = File("${context.cacheDir}/image/cache.png")
if (file.exists()){
file.delete()
}
file.outputStream().apply {
bitmap.compress(Bitmap.CompressFormat.PNG,100,this)
}
val imageUri = FileProvider.getUriForFile(
context,
"com.lsp.view.fileprovider",
file
)
context.grantUriPermission("com.lsp.view",imageUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "image/*"
shareIntent.putExtra(Intent.EXTRA_STREAM,imageUri)
val intent = Intent.createChooser(shareIntent,R.string.title_share.toString())
val resInfoList:List<ResolveInfo> = if (Build.VERSION.SDK_INT<33) {
context.packageManager
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}else{
context.packageManager.queryIntentActivities(intent,PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
}
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
context.grantUriPermission(
packageName,
imageUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
context.startActivity(intent)
}
}
fun download(file_url: String?, file_ext: String?,md5: String?){
Toast.makeText(YandViewApplication.context, R.string.toast_download_start, Toast.LENGTH_SHORT).show()
if (file_url != null) {
if (file_ext != null) {
YandViewApplication.downloadBinder?.downloadPic(file_url, file_ext,handler,md5)
}
}
}
private val handler = object : Handler(Looper.myLooper()!!){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when(msg.obj){
DOWNLOAD_SUCCESS ->{
Toast.makeText(YandViewApplication.context, R.string.toast_download_success, Toast.LENGTH_SHORT).show()
}
DOWNLOAD_ERROR -> {
Toast.makeText(YandViewApplication.context, R.string.toast_download_fail, Toast.LENGTH_SHORT).show()
}
MD5_COMPARE_ERROR -> {
Toast.makeText(YandViewApplication.context, R.string.toast_compar_md5_fail, Toast.LENGTH_SHORT).show()
}
FILE_EXISTS -> {
Toast.makeText(YandViewApplication.context, R.string.toast_file_exist, Toast.LENGTH_SHORT).show()
}
}
}
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="100%p" android:toYDelta="0%p"
android:duration="400"/>
</set>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="0%p" android:toYDelta="100%p"
android:duration="400"/>
</set>
@@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="?attr/colorPrimary"
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
<path android:fillColor="@android:color/white" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
</vector>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#fff"/>
<corners android:radius="5dp"/>
</shape>
@@ -1,5 +0,0 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/colorPrimary" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>
@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
@@ -1,4 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
<vector android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16z"/>
</vector>
@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorPrimary"
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
</vector>
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorAccent"/>
<corners android:radius="5dp"/>
<padding android:left="10dp"
android:right="10dp"
android:top="10dp"
android:bottom="10dp"/>
</shape>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorPrimary"/>
<corners android:radius="5dp"/>
<padding android:left="10dp"
android:right="10dp"
android:top="10dp"
android:bottom="10dp"/>
</shape>
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.favtag.FavTagActivity"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:fitsSystemWindows="true"
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:id="@+id/toolbar"
app:title="@string/title_activity_favouriteTag"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/tag_recy"/>
</LinearLayout>
+16 -30
View File
@@ -5,41 +5,27 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.main.MainActivity">
tools:context=".activity.MainActivity">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/swipeRefreshLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/recyclerview"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.search.SearchBar
android:id="@+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.search.SearchView
android:id="@+id/search_view"
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_gravity="bottom"
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_anchor="@id/search_bar">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/search_tip"
android:visibility="gone"/>
</com.google.android.material.search.SearchView>
app:menu="@menu/nav_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
-165
View File
@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android" >
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:layout_height="match_parent"
tools:context=".activity.pic.PicActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:id="@+id/appbar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/ctl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:id="@+id/toolbar"/>
<ImageView
app:layout_collapseMode="parallax"
android:fitsSystemWindows="true"
android:id="@+id/titleImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:contentDescription="@string/appbar_scrolling_view_behavior"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="5dp"
app:cardCornerRadius="10dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/idRecyclerView"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tagRecyclerView"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/authorRecyclerView"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/sizeRecyclerView"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:id="@+id/float_line"
android:layout_width="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical"
android:layout_margin="15dp"
android:layout_gravity="end|bottom"
android:layout_height="wrap_content">
<LinearLayout
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ctrl"
android:orientation="vertical">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:elevation="5dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@drawable/ic_twotone_arrow_downward_24"
android:id="@+id/download"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="5dp"
android:layout_margin="10dp"
android:src="@drawable/ic_twotone_share_24"
android:id="@+id/share"/>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="80dp"
android:layout_height="80dp"
app:fabCustomSize="80dp"
android:layout_margin="10dp"
android:src="@drawable/ic_baseline_add_24"
android:elevation="5dp"
android:id="@+id/float_btn"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.github.chrisbanes.photoview.PhotoView
android:background="#000"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:id="@+id/photoView"/>
<ImageView
android:layout_margin="30dp"
android:layout_width="30dp"
android:layout_height="30dp"
android:id="@+id/back"
android:src="@drawable/ic_baseline_arrow_back_24"
android:layout_gravity="top|start"
/>
</FrameLayout>
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<TextView
android:id="@+id/fav_tag"
android:layout_margin="5dp"
android:padding="5dp"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.google.android.material.card.MaterialCardView>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.CollectFragment">
<include layout="@layout/post_list_layout"/>
</FrameLayout>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.ImageFragment">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/image_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
style="@style/Widget.Material3.BottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="@menu/image_bottom_menu" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/download_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/bottomAppBar"
app:srcCompat="@drawable/baseline_file_download_24"
android:contentDescription="@string/description_download" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/post_list_layout"/>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.search.SearchBar
android:id="@+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.search.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_anchor="@id/search_bar">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/search_tip"
android:visibility="gone"/>
</com.google.android.material.search.SearchView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
+1 -1
View File
@@ -15,7 +15,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:id="@+id/picImgae"/>
android:id="@+id/image"/>
</FrameLayout>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nav_header"
android:layout_width="match_parent"
android:layout_height="30dp">
</FrameLayout>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/swipeRefreshLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints">
<androidx.recyclerview.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/recyclerview"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:textAlignment="inherit"
android:textSize="20sp"/>
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
android:layout_margin="5dp"
style="?attr/materialCardViewFilledStyle"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<TextView
android:id="@+id/tag"
android:padding="5dp"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.google.android.material.card.MaterialCardView>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:contentDescription="@string/description_share"
android:id="@+id/share_menu_btn"
android:title="@string/title_share"
android:icon="@drawable/ic_twotone_share_24"
app:showAsAction="ifRoom"/>
<item android:id="@+id/collect_menu_btn"
android:title="@string/title_collect"
android:icon="@drawable/ic_baseline_favorite_24"
app:showAsAction="ifRoom"/>
</menu>
+7 -7
View File
@@ -2,16 +2,16 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/photo"
android:id="@+id/postListFragment"
android:icon="@drawable/ic_baseline_insert_photo_24"
android:title="@string/item_gallery"/>
<item
android:id="@+id/taglist"
android:icon="@drawable/ic_baseline_label_24"
android:id="@+id/collectFragment"
android:icon="@drawable/ic_baseline_favorite_24"
android:title="@string/item_collect"/>
<item
android:id="@+id/setting"
android:icon="@drawable/setting_icon"
android:title="@string/item_settings"/>
<!-- <item-->
<!-- android:id="@+id/setting"-->
<!-- android:icon="@drawable/setting_icon"-->
<!-- android:title="@string/item_settings"/>-->
</group>
</menu>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/search_nav"
android:icon="@drawable/ic_baseline_search_24"
app:showAsAction="always"
android:title="@string/item_search" />
</menu>
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/postListFragment">
<fragment
android:id="@+id/postListFragment"
android:name="com.lsp.view.fragment.PostListFragment"
android:label="PostListFragment" >
<action
android:id="@+id/action_postListFragment_to_imageFragment"
app:destination="@id/imageFragment"
app:enterAnim="@anim/slide_on_bottom"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/slide_out_bottom" >
</action>
<action
android:id="@+id/action_postListFragment_to_collectFragment"
app:destination="@id/collectFragment" />
</fragment>
<fragment
android:id="@+id/imageFragment"
android:name="com.lsp.view.fragment.ImageFragment"
android:label="fragment_pic"
tools:layout="@layout/fragment_image" >
</fragment>
<fragment
android:id="@+id/collectFragment"
android:name="com.lsp.view.fragment.CollectFragment"
android:label="fragment_collect"
tools:layout="@layout/fragment_collect" >
<action
android:id="@+id/action_collectFragment_to_imageFragment"
app:destination="@id/imageFragment"
app:enterAnim="@anim/slide_on_bottom"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/slide_out_bottom"/>
</fragment>
</navigation>
+1 -2
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_activity_settings">设置</string>
<string name="title_activity_favouriteTag">收藏标签</string>
<string name="item_gallery">画廊</string>
<string name="item_collect">收藏</string>
<string name="item_settings">设置</string>
@@ -24,8 +23,8 @@
<string name="toast_tag_exist">收藏过了哦</string>
<string name="toast_download_success">下载成功</string>
<string name="toast_download_fail">下载失败</string>
<string name="toast_compar_md5_fail">MD5对比失败</string>
<string name="toast_file_exist">文件已经存在</string>
<string name="toast_download_start">开始下载</string>
<string name="title_share">分享</string>
<string name="title_collect">收藏</string>
</resources>
+1 -2
View File
@@ -2,7 +2,6 @@
<string name="app_name" translatable="false">YandView</string>
<string name="title_activity_settings">SettingsActivity</string>
<string name="title_activity_settings_backup" translatable="false">SettingsActivity_backup</string>
<string name="title_activity_favouriteTag">FavouriteTag</string>
<string name="item_gallery">Gallery</string>
<string name="item_collect">Collect</string>
<string name="item_settings">Settings</string>
@@ -25,8 +24,8 @@
<string name="toast_tag_exist">This tag exist</string>
<string name="toast_download_success">Successfully download</string>
<string name="toast_download_fail">Download failed</string>
<string name="toast_compar_md5_fail">MD5 comparison failed</string>
<string name="toast_file_exist">File exists</string>
<string name="toast_download_start">Start download</string>
<string name="title_share">Share</string>
<string name="title_collect">Collect</string>
</resources>
+5
View File
@@ -8,12 +8,17 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.1.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10'
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.5"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
plugins {
id 'com.google.devtools.ksp' version '1.9.20-1.0.14' apply false
}
allprojects {
repositories {
maven { url "https://www.jitpack.io" }