Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9c1f9d176 | |||
| 5d84fc4df2 | |||
| f706720a37 | |||
| ab82ef5ff6 | |||
| b6fe7f1561 | |||
| 344f587ca7 | |||
| 7d489e5d51 | |||
| 1cf4d6cb3d | |||
| 42c955be6d | |||
| b7f90e0848 | |||
| 329da172e8 | |||
| 1d208099f9 | |||
| a56d021a4a | |||
| b7efc85fa9 | |||
| c4f9dbb781 | |||
| f66fa1131a | |||
| 5baa502363 | |||
| 6b71b31dfc | |||
| 821c6ea0d6 | |||
| 87c6ecf74a | |||
| 65c911585c | |||
| 1398fcc658 | |||
| 6b79f68d92 | |||
| dee0b7e73d | |||
| b180dbb138 | |||
| 669770d1df | |||
| e1a9534e7a | |||
| 18f2583007 | |||
| 11e2572e09 | |||
| a0600088a7 | |||
| 6296ef6348 | |||
| c28b73edf8 | |||
| 429943b89d | |||
| c7be917ea3 | |||
| 90ac4169ff | |||
| db7682f298 | |||
| b702e95f40 | |||
| e3336b0458 | |||
| b386e87bc8 | |||
| 6edd9e6512 | |||
| 091a891394 | |||
| b9de8535c1 | |||
| 9b8832b8a6 | |||
| 72a44d5a48 | |||
| a846c84805 | |||
| 9ef7dda7dc | |||
| 54a8ece487 | |||
| d83082211e |
@@ -9,10 +9,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: set up JDK 11
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
- name: Grant execute permission for gradlew
|
||||
|
||||
@@ -7,10 +7,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: set up JDK 11
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
- name: Grant execute permission for gradlew
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Releases
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
|
||||
- name: Archive output results
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debug-outputs
|
||||
path: app/build/outputs/apk/debug
|
||||
@@ -1,13 +1 @@
|
||||
# YandView
|
||||
Android平台的yande.re和konachan非官方客户端
|
||||
- 能干什么?
|
||||
- 搜索和浏览色图(废话)
|
||||
- 收藏特定的标签或ID(仅限本地存储)
|
||||
- 支持R18过滤(基于网站官方的评级)
|
||||
|
||||
# Build
|
||||
In Terminal:
|
||||
|
||||
cd [ProjectPath]
|
||||
|
||||
gradlew assembleDebug
|
||||
|
||||
+59
-23
@@ -1,54 +1,90 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'androidx.navigation.safeargs.kotlin'
|
||||
id "com.google.devtools.ksp"
|
||||
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "30.0.3"
|
||||
compileSdkVersion = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.lsp.view"
|
||||
applicationId "moe.uni.moe"
|
||||
minSdkVersion 29
|
||||
targetSdkVersion 33
|
||||
versionCode 221022
|
||||
versionName "1.2.7"
|
||||
targetSdkVersion 34
|
||||
versionCode 240629
|
||||
versionName "2.0.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
}
|
||||
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
namespace 'com.lsp.view'
|
||||
namespace 'moe.uni.view'
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.5.1'
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.android.material:material:1.5.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')
|
||||
implementation("androidx.navigation:navigation-compose:2.7.7")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2'
|
||||
implementation 'androidx.activity:activity-compose:1.9.0'
|
||||
implementation platform('androidx.compose:compose-bom:2024.06.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.ui:ui-graphics'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.compose.material:material'
|
||||
androidTestImplementation platform('androidx.compose:compose-bom:2024.06.00')
|
||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||
|
||||
def room_version = "2.6.1"
|
||||
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-viewmodel-compose:2.8.2")
|
||||
implementation ('com.squareup.retrofit2:retrofit:2.9.0')
|
||||
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'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0"
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:name=".YandViewApplication"
|
||||
@@ -11,10 +12,20 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Material3.DayNight.NoActionBar">
|
||||
<activity
|
||||
android:name=".ui.compose.PostActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Material3.DayNight.NoActionBar" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.lsp.view.fileprovider"
|
||||
android:authorities="moe.uni.view.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
@@ -23,31 +34,11 @@
|
||||
</meta-data>
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".activity.favtag.FavTagActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".activity.setting.SettingsActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_settings" />
|
||||
|
||||
<service
|
||||
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:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.lsp.view
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import android.content.ServiceConnection
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.IBinder
|
||||
import com.lsp.view.service.DownloadService.DownloadBinder
|
||||
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 {
|
||||
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
|
||||
downloadBinder = iBinder as DownloadBinder
|
||||
}
|
||||
|
||||
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)
|
||||
statusBarHeight()
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
super.onDestroy(owner)
|
||||
unbindService(connection)
|
||||
}
|
||||
|
||||
//计算状态栏高度
|
||||
fun statusBarHeight(): Int {
|
||||
var height = 0
|
||||
val resourceId = resources.getIdentifier(
|
||||
"status_bar_height",
|
||||
"dimen",
|
||||
"android"
|
||||
)
|
||||
if (resourceId > 0) {
|
||||
height = resources.getDimensionPixelSize(resourceId)
|
||||
}
|
||||
return height
|
||||
}
|
||||
|
||||
companion object {
|
||||
var context: Context? = null
|
||||
private set
|
||||
var downloadBinder: DownloadBinder? = null
|
||||
private set
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.lsp.view.activity
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
transparentStatusBar()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.setDecorFitsSystemWindows(false)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
//设置导航栏透明
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||
}
|
||||
}
|
||||
private fun transparentStatusBar(){
|
||||
// 设置状态栏颜色为透明
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
//状态栏反色
|
||||
WindowCompat.getInsetsController(window,window.decorView).isAppearanceLightStatusBars = !isDarkMode()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前是否为深色模式
|
||||
* 深色模式的值为:0x21
|
||||
* 浅色模式的值为:0x11
|
||||
* @return true 为是深色模式 false为不是深色模式
|
||||
*/
|
||||
private fun isDarkMode(): Boolean {
|
||||
return resources.configuration.uiMode == 0x21
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,83 +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.widget.Toolbar
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.flexbox.*
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
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.repository.bean.Tags
|
||||
|
||||
class FavTagActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_fav_tag)
|
||||
|
||||
|
||||
//Toolbar相关
|
||||
val toolbar = findViewById<Toolbar>(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
|
||||
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,226 +0,0 @@
|
||||
package com.lsp.view.activity.main
|
||||
|
||||
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
|
||||
import androidx.core.view.WindowInsetsCompat.Type.ime
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
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.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.setting.SettingsActivity
|
||||
import com.lsp.view.model.MainViewModel
|
||||
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
private lateinit var search: EditText
|
||||
private var shortAnnotationDuration: Int = 0
|
||||
private var username: String? = ""
|
||||
private var nowSourceName: String? = null
|
||||
val TAG = javaClass.simpleName
|
||||
private var safeMode: Boolean = true //安全模式
|
||||
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)
|
||||
|
||||
viewModel.uiState.value.isRefreshing.observe(this) {
|
||||
refresh.isRefreshing = it
|
||||
}
|
||||
|
||||
refresh.setOnRefreshListener {
|
||||
viewModel.fetchPostByRefresh()
|
||||
}
|
||||
|
||||
viewModel.adapter.apply {
|
||||
setLoadMoreListener(object :PostAdapter.OnScrollToBottom{
|
||||
override fun event(position: Int) {
|
||||
viewModel.appendPost()
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
findViewById<RecyclerView>(R.id.recyclerview).apply {
|
||||
val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
|
||||
this.layoutManager = layoutManager
|
||||
this.adapter = viewModel.adapter
|
||||
}
|
||||
|
||||
search = findViewById(R.id.search)
|
||||
|
||||
supportActionBar?.let {
|
||||
it.setDisplayHomeAsUpEnabled(true)
|
||||
it.setHomeAsUpIndicator(R.drawable.ic_baseline_menu_24)
|
||||
}
|
||||
|
||||
shortAnnotationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
|
||||
search.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
viewModel.fetchNewPostBySearch(search.text.toString())
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
|
||||
|
||||
//侧边栏
|
||||
val sp = getSharedPreferences("username", 0)
|
||||
username = sp.getString("username", null)
|
||||
|
||||
val nav = findViewById<NavigationView>(R.id.nav)
|
||||
|
||||
//加载导航栏列表
|
||||
nav.setCheckedItem(R.id.photo)
|
||||
//设置侧边栏点击逻辑
|
||||
nav.setNavigationItemSelectedListener {
|
||||
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
|
||||
when (it.itemId) {
|
||||
//画廊
|
||||
R.id.photo -> {
|
||||
viewModel.fetchPostByRefresh()
|
||||
drawerLayout.closeDrawers()
|
||||
true
|
||||
}
|
||||
//设置
|
||||
R.id.setting -> {
|
||||
val intent = Intent(this, SettingsActivity::class.java)
|
||||
startActivity(intent)
|
||||
drawerLayout.closeDrawers()
|
||||
false
|
||||
|
||||
}
|
||||
R.id.taglist -> {
|
||||
val intent = Intent(this, FavTagActivity::class.java)
|
||||
startActivity(intent)
|
||||
drawerLayout.closeDrawers()
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
//加载源改变
|
||||
val configSp = getSharedPreferences("com.lsp.view_preferences", 0)
|
||||
if (nowSourceName != configSp.getString("source_name",null)){
|
||||
viewModel.fetchPostByRefresh()
|
||||
nowSourceName = configSp.getString("source_name",null)
|
||||
}
|
||||
|
||||
if (safeMode != configSp.getBoolean("safe_mode",true)){
|
||||
viewModel.fetchPostByRefresh()
|
||||
safeMode = configSp.getBoolean("safe_mode",true)
|
||||
}
|
||||
|
||||
search.setText("")
|
||||
|
||||
}
|
||||
|
||||
//隐藏搜索栏
|
||||
private fun hiddenSearchBar() {
|
||||
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_menu_24)
|
||||
search.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(shortAnnotationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
search.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
hideIm()
|
||||
viewModel.uiState.value.switchShowSearchBar()
|
||||
}
|
||||
|
||||
//现实搜索栏
|
||||
private fun showSearchBar() {
|
||||
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_arrow_back_24)
|
||||
search.apply {
|
||||
alpha = 0f
|
||||
visibility = View.VISIBLE
|
||||
animate()
|
||||
.alpha(1f)
|
||||
.setDuration(shortAnnotationDuration.toLong())
|
||||
.setListener(null)
|
||||
}
|
||||
viewModel.uiState.value.switchShowSearchBar()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
if (viewModel.uiState.value.showSearchBar){
|
||||
hiddenSearchBar()
|
||||
}else {
|
||||
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
|
||||
drawerLayout.openDrawer(GravityCompat.START)
|
||||
}
|
||||
}
|
||||
R.id.search_nav -> {
|
||||
if (viewModel.uiState.value.showSearchBar) {
|
||||
viewModel.fetchNewPostBySearch(search.text.toString())
|
||||
hideIm()
|
||||
|
||||
}
|
||||
|
||||
if (search.visibility == View.GONE) {
|
||||
showSearchBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
private fun hideIm() {
|
||||
WindowCompat.getInsetsController(this.window,window.decorView).hide(ime())
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.toolbar_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package com.lsp.view.activity.main
|
||||
|
||||
import android.content.Context
|
||||
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.repository.bean.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 pushNewData(list: ArrayList<YandPost>) {
|
||||
val oldSize = postList.size
|
||||
postList.clear()
|
||||
notifyItemRangeRemoved(0,oldSize)
|
||||
postList.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", 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)
|
||||
}
|
||||
|
||||
|
||||
return viewHolder
|
||||
}
|
||||
|
||||
private lateinit var mScrollToBottom: OnScrollToBottom
|
||||
|
||||
interface OnScrollToBottom {
|
||||
fun event(position: Int)
|
||||
}
|
||||
|
||||
fun setLoadMoreListener(mLoadMoreListener: OnScrollToBottom) {
|
||||
this.mScrollToBottom = mLoadMoreListener
|
||||
}
|
||||
|
||||
private fun preLoad(p:Int){
|
||||
if (p%20==0){
|
||||
//执行预加载
|
||||
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 = postList[index].sample_url
|
||||
val glideUrl = GlideUrl(
|
||||
source,
|
||||
LazyHeaders.Builder().addHeader("User-Agent", UA)
|
||||
.build()
|
||||
)
|
||||
Glide.with(context).download(glideUrl).preload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
preLoad(position)
|
||||
|
||||
val post = postList[position]
|
||||
|
||||
val source: String = post.sample_url
|
||||
|
||||
val height = postList[position].sample_height
|
||||
val width = postList[position].sample_width
|
||||
|
||||
//图片过长则缩减高度以保证显示完整
|
||||
if (width > height){
|
||||
holder.picImage.layoutParams.height = height / 2
|
||||
}else {
|
||||
holder.picImage.layoutParams.height = height
|
||||
}
|
||||
|
||||
|
||||
val glideUrl = GlideUrl(
|
||||
source,
|
||||
LazyHeaders.Builder().addHeader("User-Agent", UA)
|
||||
.build()
|
||||
)
|
||||
Glide.with(context).load(glideUrl).into(holder.picImage)
|
||||
|
||||
if (position == postList.size - 1 && postList.size > 6) {
|
||||
//到达底部
|
||||
mScrollToBottom.event(position)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
val size = postList.size
|
||||
return size
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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.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.BaseActivity
|
||||
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 : BaseActivity() {
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.lsp.view.activity.setting
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.lsp.view.YandViewApplication
|
||||
import com.lsp.view.R
|
||||
import com.lsp.view.activity.BaseActivity
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.settings_activity)
|
||||
|
||||
val toolbar = findViewById<Toolbar>(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
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, SettingsFragment())
|
||||
.commit()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.lsp.view.repository.exception
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
|
||||
class NetworkErrorException(override val message: String?) : Exception() {
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.lsp.view.repository.exception
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
class UnableConstructObjectException(override val message: String?):Exception(message) {
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package com.lsp.view.service
|
||||
|
||||
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
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件MD5
|
||||
*/
|
||||
fun getFileMD5(path: String?): String? {
|
||||
if (path.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
var digest: MessageDigest? = null
|
||||
var fileIS: FileInputStream? = null
|
||||
val buffer = ByteArray(1024)
|
||||
var len = 0
|
||||
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)
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return mBinder
|
||||
}
|
||||
}
|
||||
@@ -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,21 @@
|
||||
package moe.uni.view
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import android.content.Context
|
||||
|
||||
class YandViewApplication : Application(), DefaultLifecycleObserver {
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super<Application>.onCreate()
|
||||
context = applicationContext
|
||||
}
|
||||
|
||||
companion object {
|
||||
var context: Context? = 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,53 @@
|
||||
package moe.uni.view.bean
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import java.io.Serializable
|
||||
|
||||
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,
|
||||
open val tags:String?
|
||||
): Parcelable,Serializable {
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString()!!,
|
||||
parcel.readString()!!,
|
||||
parcel.readString()!!,
|
||||
parcel.readString()!!,
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readString()!!
|
||||
)
|
||||
|
||||
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)
|
||||
dest.writeString(tags)
|
||||
}
|
||||
|
||||
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,13 @@
|
||||
package moe.uni.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("id") override val postId: String,
|
||||
rating: String,
|
||||
tags:String?
|
||||
):Post(postId, rating, sampleUrl,fileUrl,sampleHeight,sampleWidth,tags)
|
||||
@@ -0,0 +1,27 @@
|
||||
package moe.uni.view.common
|
||||
|
||||
class Config(val safeMode:Boolean,val source:String) {
|
||||
companion object{
|
||||
fun setConfig(config: Config){
|
||||
PreUtils.putString(PreKV.SOURCE_NAME,config.source)
|
||||
PreUtils.putBool(PreKV.SAFE_MODE,config.safeMode)
|
||||
}
|
||||
|
||||
fun getConfig():Config{
|
||||
return Config(PreUtils.getBool(PreKV.SAFE_MODE,true),PreUtils.getString(PreKV.SOURCE_NAME,SOURCE.YANDE_RE.values.first()))
|
||||
}
|
||||
|
||||
fun setSafeMode(value:Boolean){
|
||||
PreUtils.putBool(PreKV.SAFE_MODE,value)
|
||||
}
|
||||
|
||||
fun getSafeMode():Boolean{
|
||||
return PreUtils.getBool(PreKV.SAFE_MODE,true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object SOURCE {
|
||||
val YANDE_RE = mapOf("yande.re" to "https://yande.re/")
|
||||
val KONACHAN = mapOf("konachan" to "https://konachan.com/")
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package moe.uni.view.common
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import moe.uni.view.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
data class ImageInfo(val path: String, val name: String, val uri: Uri)
|
||||
|
||||
@SuppressLint("QueryPermissionsNeeded")
|
||||
fun share(url: String, context: Context) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val imageInfo = getImageInfo(url, context)
|
||||
context.grantUriPermission(
|
||||
context.packageName,
|
||||
imageInfo.uri,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.type = "image/*"
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, imageInfo.uri)
|
||||
shareIntent.putExtra(Intent.EXTRA_TITLE, imageInfo.name)
|
||||
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,
|
||||
imageInfo.uri,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun setWallpaper(url: String,context: Context) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val imageInfo = getImageInfo(url, context)
|
||||
val intent = Intent(Intent.ACTION_ATTACH_DATA)
|
||||
intent.setDataAndType(imageInfo.uri, "image/*")
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getImageInfo(url: String, context: Context): ImageInfo {
|
||||
|
||||
val imageLoader = ImageLoader.Builder(context)
|
||||
.build()
|
||||
|
||||
val imageRequest = ImageRequest.Builder(context).data(url).allowHardware(true).build()
|
||||
val cacheImage = imageLoader.execute(imageRequest)
|
||||
val bitmap = cacheImage.drawable?.toBitmap()
|
||||
|
||||
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,
|
||||
"moe.uni.view.fileprovider",
|
||||
file
|
||||
)
|
||||
return ImageInfo(path = file.path, name = fileName, uri = imageUri)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package moe.uni.view.common
|
||||
|
||||
object PreKV {
|
||||
const val SOURCE_NAME = "source_name"
|
||||
const val SAFE_MODE = "safe_mode"
|
||||
}
|
||||
|
||||
object Pre{
|
||||
const val NAME = "moe.uni.view_preferences"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package moe.uni.view.common
|
||||
|
||||
import android.content.Context
|
||||
import moe.uni.view.YandViewApplication
|
||||
|
||||
object PreUtils {
|
||||
private val PRE = YandViewApplication.context!!.getSharedPreferences(Pre.NAME,Context.MODE_PRIVATE)
|
||||
fun getString(key:String,default:String):String{
|
||||
return PRE.getString(key,default)!!
|
||||
}
|
||||
|
||||
fun putString(key: String,value:String){
|
||||
PRE.edit().putString(key,value).apply()
|
||||
}
|
||||
|
||||
fun getBool(key: String,default:Boolean):Boolean{
|
||||
return PRE.getBoolean(key,default)
|
||||
}
|
||||
|
||||
fun putBool(key: String,value: Boolean){
|
||||
PRE.edit().putBoolean(key, value).apply()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package moe.uni.view.repository.datasource
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import moe.uni.view.repository.datasource.dao.CollectDao
|
||||
import moe.uni.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 moe.uni.view.repository.datasource
|
||||
|
||||
import moe.uni.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 moe.uni.view.repository.datasource
|
||||
|
||||
import moe.uni.view.repository.datasource.dao.CollectDao
|
||||
import moe.uni.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 moe.uni.view.repository.datasource.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import moe.uni.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,37 @@
|
||||
package moe.uni.view.repository.datasource.model
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import moe.uni.view.YandViewApplication
|
||||
import moe.uni.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,
|
||||
@ColumnInfo(name = "tags") override val tags:String?
|
||||
):Post(postId,rating,sampleUrl,fileUrl,sampleHeight,sampleWidth,tags){
|
||||
constructor(
|
||||
postId: String,
|
||||
sampleUrl:String,
|
||||
rating:String,
|
||||
fileUrl:String,
|
||||
sampleHeight:Int,
|
||||
sampleWeight:Int,
|
||||
tags:String?
|
||||
): this(0, postId, sampleUrl, rating, fileUrl, sampleHeight, sampleWeight,
|
||||
YandViewApplication.context?.getSharedPreferences("moe.uni.view_preferences",Context.MODE_PRIVATE)?.getString("source_name", "yande.re")!!,tags)
|
||||
companion object{
|
||||
fun Post.toCollect():Collect = Collect(this.postId,this.sampleUrl,this.rating,this.fileUrl,this.sampleHeight,this.sampleWidth ,this.tags)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package moe.uni.view.repository.exception
|
||||
|
||||
import android.util.Log
|
||||
|
||||
open class LoggerException(override val message: String?):Exception() {
|
||||
init {
|
||||
logMessage()
|
||||
}
|
||||
|
||||
private fun logMessage(){
|
||||
Log.e(this::class.java.simpleName,message.toString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package moe.uni.view.repository.exception
|
||||
|
||||
class NetworkErrorException(override val message: String?) : LoggerException(message) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package moe.uni.view.repository.exception
|
||||
|
||||
class UnableConstructObjectException(override val message: String?):LoggerException(message) {
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package moe.uni.view.repository.network
|
||||
|
||||
import moe.uni.view.repository.network.api.PostApi
|
||||
import moe.uni.view.repository.network.api.PostApiImpl
|
||||
import moe.uni.view.bean.YandPost
|
||||
import moe.uni.view.common.Config
|
||||
|
||||
class PostDataSource {
|
||||
companion object{
|
||||
const val YANDE_RE = "yande.re"
|
||||
const val KONACHAN = "konachan"
|
||||
}
|
||||
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,
|
||||
val source:String
|
||||
){
|
||||
companion object{
|
||||
fun builder(tags: String?, page: Int): Load {
|
||||
val config = Config.getConfig()
|
||||
return Load(tags,page,config.safeMode,config.source)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package moe.uni.view.repository.network
|
||||
|
||||
import moe.uni.view.bean.YandPost
|
||||
import moe.uni.view.common.Config
|
||||
|
||||
|
||||
class PostRepository {
|
||||
private val dataSource = PostDataSource()
|
||||
|
||||
//获取post
|
||||
suspend fun fetchPostData(searchTarget :String?,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)
|
||||
val dataList = dataSource.fetchNewPost(load)
|
||||
|
||||
return if (Config.getSafeMode()){
|
||||
ArrayList(dataList.filter { it.rating == "s" })
|
||||
}else{
|
||||
dataList
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+6
-9
@@ -1,12 +1,10 @@
|
||||
package com.lsp.view.repository.api
|
||||
package moe.uni.view.repository.network.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 moe.uni.view.repository.network.Load
|
||||
import moe.uni.view.bean.Post
|
||||
import moe.uni.view.bean.YandPost
|
||||
import moe.uni.view.repository.exception.NetworkErrorException
|
||||
import retrofit2.Call
|
||||
import java.net.SocketException
|
||||
|
||||
interface PostApi {
|
||||
private val TAG: String
|
||||
@@ -22,7 +20,6 @@ interface PostApi {
|
||||
val execute = service.execute()
|
||||
return if (execute.isSuccessful) {
|
||||
if (execute.body() != null) {
|
||||
Log.e(TAG, execute.body().toString())
|
||||
execute.body()!!
|
||||
|
||||
} else {
|
||||
@@ -38,5 +35,5 @@ interface PostApi {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchNewPost(load:Load):ArrayList<YandPost>
|
||||
suspend fun fetchNewPost(load: Load):ArrayList<YandPost>
|
||||
}
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
package com.lsp.view.repository.api
|
||||
package moe.uni.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 moe.uni.view.repository.network.Load
|
||||
import moe.uni.view.repository.network.retrofit.PostService
|
||||
import moe.uni.view.repository.network.retrofit.ServiceCreator
|
||||
import moe.uni.view.bean.YandPost
|
||||
import retrofit2.Call
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.lsp.view.retrofit
|
||||
package moe.uni.view.repository.network.retrofit
|
||||
|
||||
import com.lsp.view.repository.bean.YandPost
|
||||
import moe.uni.view.bean.YandPost
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
+9
-1
@@ -1,10 +1,15 @@
|
||||
package com.lsp.view.retrofit
|
||||
package moe.uni.view.repository.network.retrofit
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
object ServiceCreator {
|
||||
|
||||
val CLIENT = OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}).build()
|
||||
|
||||
/**
|
||||
* @param serviceClass:所属类的Service接口
|
||||
@@ -12,8 +17,11 @@ object ServiceCreator {
|
||||
*
|
||||
*/
|
||||
fun <T> create(serviceClass: Class<T>, source: String): T {
|
||||
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(source)
|
||||
.client(CLIENT)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
return retrofit.create(serviceClass)
|
||||
@@ -0,0 +1,88 @@
|
||||
package moe.uni.view.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.*
|
||||
import moe.uni.view.repository.network.retrofit.ServiceCreator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import okhttp3.Request
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class Result(val success:Boolean,val message: String,val exception: Exception?){
|
||||
companion object{
|
||||
fun success(message:String):Result{
|
||||
return Result(true,message,null)
|
||||
}
|
||||
|
||||
fun failure(message: String = "",exception: Exception? = null):Result{
|
||||
return Result(false,message,exception)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
class DownloadService : Service() {
|
||||
private val mBinder = DownloadBinder(this)
|
||||
|
||||
class DownloadBinder(val context: Context) : Binder() {
|
||||
|
||||
fun downloadImage(fileUrl: String): Deferred<Result> {
|
||||
return CoroutineScope(Dispatchers.IO).async {
|
||||
val fileDir =
|
||||
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/")
|
||||
if (!fileDir.exists()) {
|
||||
fileDir.mkdirs()
|
||||
}
|
||||
val split = fileUrl.split("/")
|
||||
val fileName = split[split.size - 1]
|
||||
val file =
|
||||
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$fileName")
|
||||
if (file.exists()) {
|
||||
return@async Result.failure("File exists")
|
||||
}
|
||||
val fos = FileOutputStream(file)
|
||||
try {
|
||||
val request = Request.Builder()
|
||||
.url(fileUrl)
|
||||
.build()
|
||||
val response = ServiceCreator.CLIENT.newCall(request).execute()
|
||||
val responseData = response.body?.bytes()
|
||||
return@async 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("Connection timed out")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(exception = e)
|
||||
} finally {
|
||||
fos.close()
|
||||
}
|
||||
|
||||
return@async Result.failure(message = "Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return mBinder
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package moe.uni.view.ui.compose
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import moe.uni.view.ui.compose.theme.LspViewTheme
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import moe.uni.view.service.DownloadService
|
||||
import moe.uni.view.ui.compose.screen.DetailScreen
|
||||
import moe.uni.view.ui.compose.screen.PostListScreen
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val TAG = "Compose_PostActivity"
|
||||
|
||||
class PostActivity : ComponentActivity() {
|
||||
lateinit var downloadBinder: DownloadService.DownloadBinder
|
||||
val viewModel: PostViewModel by viewModels<PostViewModel>()
|
||||
|
||||
private val connection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
|
||||
downloadBinder = iBinder as DownloadService.DownloadBinder
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(componentName: ComponentName) {}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
LspViewTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
App()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.downloadAction.observe(this) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val result =
|
||||
downloadBinder.downloadImage(viewModel.uiState.value.selectPost?.fileUrl ?: "")
|
||||
.await()
|
||||
viewModel.setDownloadResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.downloadResult.observe(this){
|
||||
val message = it.message
|
||||
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
val serviceIntent = Intent(this, DownloadService::class.java)
|
||||
bindService(serviceIntent, connection, BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unbindService(connection)
|
||||
}
|
||||
}
|
||||
|
||||
const val NAV_ROUTE_POSTLIST_SCREEN = "NAV_ROUTE_POSTLIST_SCREEN"
|
||||
const val NAV_ROUTE_DETAIL_SCREEN = "NAV_ROUTE_DETAIL_SCREEN"
|
||||
|
||||
@Composable
|
||||
fun App(viewModel: PostViewModel = viewModel()) {
|
||||
val navController = rememberNavController()
|
||||
|
||||
NavHost(navController, startDestination = NAV_ROUTE_POSTLIST_SCREEN) {
|
||||
composable(NAV_ROUTE_POSTLIST_SCREEN) {
|
||||
PostListScreen(navController = navController, viewModel = viewModel)
|
||||
}
|
||||
|
||||
composable(NAV_ROUTE_DETAIL_SCREEN) {
|
||||
DetailScreen(navController = navController, viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package moe.uni.view.ui.compose
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import moe.uni.view.bean.Post
|
||||
|
||||
data class PostUiState(
|
||||
var refresh:MutableState<Boolean> = mutableStateOf(true),
|
||||
var searchTarget: MutableState<String> = mutableStateOf(""),
|
||||
var page:Int = 1,
|
||||
var selectPost: Post? = null
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
package moe.uni.view.ui.compose
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import moe.uni.view.bean.YandPost
|
||||
import moe.uni.view.repository.exception.NetworkErrorException
|
||||
import moe.uni.view.repository.network.PostRepository
|
||||
import moe.uni.view.service.Result
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PostViewModel:ViewModel() {
|
||||
private val _uiState = MutableStateFlow(PostUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val postData:MutableStateFlow<List<YandPost>> = MutableStateFlow(arrayListOf())
|
||||
private val repository by lazy {
|
||||
PostRepository()
|
||||
}
|
||||
val downloadAction = MutableLiveData<Unit>()
|
||||
val downloadResult = MutableLiveData<Result>()
|
||||
|
||||
init {
|
||||
initData()
|
||||
}
|
||||
|
||||
private fun initData(){
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
fetchPost()
|
||||
}
|
||||
}
|
||||
|
||||
fun actionDownload(){
|
||||
downloadAction.postValue(Unit)
|
||||
}
|
||||
|
||||
suspend fun fetchPost(searchTarget: String = _uiState.value.searchTarget.value,refresh:Boolean = false){
|
||||
_uiState.value.refresh.value = true
|
||||
_uiState.value.searchTarget.value = searchTarget
|
||||
try {
|
||||
if (refresh){
|
||||
_uiState.value.page = 0
|
||||
}
|
||||
val data = repository.fetchPostData(
|
||||
searchTarget,
|
||||
_uiState.value.page
|
||||
)
|
||||
postData.value = if (refresh){
|
||||
data
|
||||
}else{
|
||||
_uiState.value.page ++
|
||||
postData.value + data
|
||||
}
|
||||
} catch (e: NetworkErrorException) {
|
||||
//todo network error
|
||||
}
|
||||
_uiState.value.refresh.value = false
|
||||
}
|
||||
|
||||
fun setDownloadResult(result: Result){
|
||||
downloadResult.postValue(result)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package moe.uni.view.ui.compose.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.rememberTransformableState
|
||||
import androidx.compose.foundation.gestures.transformable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.AsyncImage
|
||||
import moe.uni.view.R
|
||||
import moe.uni.view.common.setWallpaper
|
||||
import moe.uni.view.common.share
|
||||
import moe.uni.view.ui.compose.PostViewModel
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DetailScreen(navController: NavController, viewModel: PostViewModel) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
var scale by remember { mutableFloatStateOf(1f) }
|
||||
var rotation by remember { mutableFloatStateOf(0f) }
|
||||
val state = rememberTransformableState { zoomChange, _, rotationChange ->
|
||||
scale *= zoomChange
|
||||
rotation += rotationChange
|
||||
}
|
||||
val context = LocalContext.current
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(Color.Black)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
AsyncImage(
|
||||
model = uiState.selectPost?.sampleUrl,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer(
|
||||
scaleX = scale,
|
||||
scaleY = scale,
|
||||
rotationZ = rotation,
|
||||
)
|
||||
.transformable(state = state)
|
||||
.background(Color.Black)
|
||||
)
|
||||
|
||||
Column {
|
||||
TopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||
actionIconContentColor = Color.White,
|
||||
navigationIconContentColor = Color.White
|
||||
),
|
||||
title = {},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = R.string.description_back.toString())
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
viewModel.actionDownload()
|
||||
Toast.makeText(context, R.string.toast_download_start, Toast.LENGTH_SHORT).show()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_twotone_arrow_downward_24),
|
||||
contentDescription = R.string.description_download.toString()
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
uiState.selectPost?.let {
|
||||
share(it.sampleUrl, context)
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Share,
|
||||
contentDescription = R.string.description_share.toString(),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
uiState.selectPost?.let {
|
||||
setWallpaper(it.sampleUrl, context)
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_twotone_wallpaper_24),
|
||||
contentDescription = R.string.description_use_to_wallpaper.toString(),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package moe.uni.view.ui.compose.screen
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.compose.SubcomposeAsyncImageContent
|
||||
import moe.uni.view.bean.Post
|
||||
import moe.uni.view.common.Config
|
||||
import moe.uni.view.ui.compose.NAV_ROUTE_DETAIL_SCREEN
|
||||
import moe.uni.view.ui.compose.PostViewModel
|
||||
import moe.uni.view.ui.compose.widget.SearchBar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(
|
||||
ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class,
|
||||
ExperimentalMaterialApi::class
|
||||
)
|
||||
@Composable
|
||||
fun PostListScreen(
|
||||
navController: NavController, viewModel: PostViewModel
|
||||
) {
|
||||
val postList by viewModel.postData.collectAsState()
|
||||
val listState = rememberLazyStaggeredGridState()
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
var lastScrollOffset by remember {
|
||||
mutableIntStateOf(0)
|
||||
}
|
||||
var lastScrollPosition by remember {
|
||||
mutableIntStateOf(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 1 scroll up
|
||||
* -1 scroll down
|
||||
* 0 default
|
||||
*/
|
||||
var scrollDirectionState by remember {
|
||||
mutableIntStateOf(0)
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
var searchBarHeightSize by remember {
|
||||
mutableStateOf(0.dp)
|
||||
}
|
||||
val density = LocalDensity.current
|
||||
|
||||
val searchBarPadding by remember {
|
||||
mutableStateOf(24.dp)
|
||||
}
|
||||
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
|
||||
val refreshing by remember {
|
||||
uiState.refresh
|
||||
}
|
||||
|
||||
val pullRefreshState = rememberPullRefreshState(refreshing = refreshing, onRefresh = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
viewModel.fetchPost(refresh = true)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
|
||||
snapshotFlow { listState.layoutInfo }.map { layoutInfo ->
|
||||
if (lastScrollOffset < listState.firstVisibleItemScrollOffset && lastScrollPosition < listState.firstVisibleItemIndex) {
|
||||
scrollDirectionState = -1
|
||||
}
|
||||
if (lastScrollOffset > listState.firstVisibleItemScrollOffset && lastScrollPosition > listState.firstVisibleItemIndex) {
|
||||
scrollDirectionState = 1
|
||||
}
|
||||
lastScrollOffset = listState.firstVisibleItemScrollOffset
|
||||
lastScrollPosition = listState.firstVisibleItemIndex
|
||||
|
||||
layoutInfo.visibleItemsInfo.lastOrNull()?.index == postList.lastIndex
|
||||
}.distinctUntilChanged().collect { reachedEnd ->
|
||||
//避免初次加载数据时,list未绘制导致被判断为到达底部
|
||||
if (reachedEnd && listState.firstVisibleItemIndex != 0) {
|
||||
withContext(Dispatchers.IO) {
|
||||
viewModel.fetchPost()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
state = listState,
|
||||
columns = StaggeredGridCells.Adaptive(200.dp),
|
||||
verticalItemSpacing = 4.dp,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
content = {
|
||||
//空置一行显示search bar
|
||||
item(key = 0, span = StaggeredGridItemSpan.FullLine) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(searchBarHeightSize + searchBarPadding)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
items(items = postList, key = {
|
||||
it.postId//提供key以保持列表顺序稳定
|
||||
}) {
|
||||
Row(Modifier.animateItemPlacement(tween(durationMillis = 250))) {
|
||||
PostItem(it, clickable = {
|
||||
viewModel.uiState.value.selectPost = it
|
||||
navController.navigate(NAV_ROUTE_DETAIL_SCREEN)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.background(Color.Transparent)
|
||||
.pullRefresh(pullRefreshState)
|
||||
.padding(horizontal = 4.dp)
|
||||
|
||||
)
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing, pullRefreshState,
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(searchBarHeightSize + searchBarPadding)
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = scrollDirectionState != -1,
|
||||
enter = slideInVertically(
|
||||
// 进入动画,从顶部滑下
|
||||
initialOffsetY = { -it },
|
||||
animationSpec = tween(durationMillis = 300)
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
// 退出动画,向上滑动消失
|
||||
targetOffsetY = { -it },
|
||||
animationSpec = tween(durationMillis = 300)
|
||||
)
|
||||
) {
|
||||
|
||||
SearchBar(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp, vertical = searchBarPadding / 2)
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.onGloballyPositioned {
|
||||
searchBarHeightSize = with(density) {
|
||||
it.size.height.toDp()
|
||||
}
|
||||
},
|
||||
searchTarget = uiState.searchTarget.value,
|
||||
searchEvent = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
viewModel.fetchPost(it, refresh = true)
|
||||
listState.animateScrollToItem(index = 0)
|
||||
}
|
||||
}, menuButtonAction = {
|
||||
showBottomSheet = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showBottomSheet) {
|
||||
var safeModel by remember {
|
||||
mutableStateOf(Config.getSafeMode())
|
||||
}
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { showBottomSheet = false },
|
||||
modifier = Modifier.height(400.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Safe mode",
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Switch(checked = safeModel, onCheckedChange = {
|
||||
safeModel = it
|
||||
Config.setSafeMode(it)
|
||||
})
|
||||
}
|
||||
MaterialTheme.shapes.large
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PostItem(post: Post, clickable: (Post) -> Unit) {
|
||||
Card {
|
||||
SubcomposeAsyncImage(model = post.sampleUrl,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
contentDescription = null,
|
||||
loading = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(300.dp)
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(50.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
},
|
||||
success = {
|
||||
SubcomposeAsyncImageContent()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.clickable { clickable.invoke(post) })
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package moe.uni.view.ui.compose.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
@@ -0,0 +1,70 @@
|
||||
package moe.uni.view.ui.compose.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LspViewTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colorScheme.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package moe.uni.view.ui.compose.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,86 @@
|
||||
package moe.uni.view.ui.compose.widget
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
|
||||
@Composable
|
||||
fun SearchBar(modifier: Modifier = Modifier,searchTarget: String = "", searchEvent: (String) -> Unit,menuButtonAction: () -> Unit) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.onPrimary, shape = RoundedCornerShape(56.dp)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(15.dp)
|
||||
.height(30.dp)
|
||||
.width(30.dp)
|
||||
.clickable {
|
||||
menuButtonAction.invoke()
|
||||
}
|
||||
)
|
||||
|
||||
var input: String by remember { mutableStateOf(searchTarget) }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val forceManager = LocalFocusManager.current
|
||||
BasicTextField(
|
||||
value = input,
|
||||
onValueChange = {
|
||||
input = it
|
||||
},
|
||||
singleLine = true,
|
||||
textStyle = TextStyle(fontSize = 20.sp, color = MaterialTheme.colorScheme.onSurfaceVariant),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions {
|
||||
keyboardController?.hide()
|
||||
forceManager.clearFocus()
|
||||
searchEvent.invoke(input)
|
||||
},
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.wrapContentHeight()
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(15.dp)
|
||||
.height(30.dp)
|
||||
.width(30.dp)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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>
|
||||
@@ -0,0 +1,6 @@
|
||||
<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="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
||||
</vector>
|
||||
@@ -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 +1,6 @@
|
||||
<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">
|
||||
<vector android:autoMirrored="true" 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="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>
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
android:id="@+id/drawerLayout"
|
||||
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.main.MainActivity">
|
||||
|
||||
<RelativeLayout
|
||||
|
||||
android:id="@+id/rl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activity.main.MainActivity">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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"
|
||||
app:menu="@menu/toolbar_menu"
|
||||
android:id="@+id/toolbar"
|
||||
>
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="20sp"
|
||||
android:id="@+id/search"
|
||||
android:imeOptions="actionSearch"
|
||||
android:singleLine="true"
|
||||
android:visibility="gone" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
<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>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/nav"
|
||||
android:layout_gravity="start"
|
||||
app:menu="@menu/nav_menu"
|
||||
app:headerLayout="@layout/nav_header"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- TODO 侧边连:登录 收藏 Tag列表-->
|
||||
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:elevation="20dp"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:cardCornerRadius="4dp"
|
||||
android:layout_margin="5dp"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:layout_height="wrap_content">
|
||||
<FrameLayout
|
||||
android:id="@+id/item_pic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:id="@+id/picImgae"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -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>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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"
|
||||
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"
|
||||
/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/photo"
|
||||
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:title="@string/item_collect"/>
|
||||
<item
|
||||
android:id="@+id/setting"
|
||||
android:icon="@drawable/setting_icon"
|
||||
android:title="@string/item_settings"/>
|
||||
</group>
|
||||
</menu>
|
||||
@@ -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>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<background android:drawable="@color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<background android:drawable="@color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
|
||||
</adaptive-icon>
|
||||
@@ -1,13 +1,14 @@
|
||||
<?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>
|
||||
<string name="item_search">搜索</string>
|
||||
<string name="description_download">下载</string>
|
||||
<string name="description_share">分享</string>
|
||||
<string name="description_use_to_wallpaper">用作壁纸</string>
|
||||
<string name="description_back">返回</string>
|
||||
<string name="activity_login_login">登录</string>
|
||||
<string name="activity_login_account">账户</string>
|
||||
<string name="activity_login_password">密码</string>
|
||||
@@ -24,8 +25,9 @@
|
||||
<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>
|
||||
<string name="tags">标签</string>
|
||||
</resources>
|
||||
@@ -1,12 +0,0 @@
|
||||
<resources>
|
||||
<!-- Reply Preference -->
|
||||
<string-array name="reply_entries">
|
||||
<item>Reply</item>
|
||||
<item>Reply to all</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="reply_values">
|
||||
<item>reply</item>
|
||||
<item>reply_all</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -2,13 +2,14 @@
|
||||
<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>
|
||||
<string name="item_search">Search</string>
|
||||
<string name="description_download">Download</string>
|
||||
<string name="description_share">Share</string>
|
||||
<string name="description_use_to_wallpaper">Use to wallpaper</string>
|
||||
<string name="description_back">Back</string>
|
||||
<string name="activity_login_login">Login</string>
|
||||
<string name="activity_login_account">Account</string>
|
||||
<string name="activity_login_password">Password</string>
|
||||
@@ -25,8 +26,9 @@
|
||||
<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>
|
||||
<string name="tags">Tags</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Material3.DayNight.NoActionBar" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
@@ -1,8 +1,7 @@
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<PreferenceCategory app:title="@string/title_setting_load_source"
|
||||
>
|
||||
<PreferenceCategory app:title="@string/title_setting_load_source">
|
||||
|
||||
<ListPreference
|
||||
app:defaultValue="yande.re"
|
||||
@@ -16,8 +15,8 @@
|
||||
<PreferenceCategory app:title="@string/title_setting_safe_check">
|
||||
<CheckBoxPreference
|
||||
app:defaultValue="true"
|
||||
app:title="@string/setting_item_safe_model"
|
||||
app:key="safe_mode"/>
|
||||
app:key="safe_mode"
|
||||
app:title="@string/setting_item_safe_model" />
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
|
||||
+9
-3
@@ -1,19 +1,25 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.4.31"
|
||||
ext.kotlin_version = "1.8.10"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10'
|
||||
classpath 'com.android.tools.build:gradle:8.3.2'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10'
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
|
||||
|
||||
// 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
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "https://www.jitpack.io" }
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Mon Sep 12 00:20:10 CST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
include ':app'
|
||||
rootProject.name = "LspView"
|
||||
rootProject.name = "YandView"
|
||||
Reference in New Issue
Block a user