diff --git a/app/build.gradle b/app/build.gradle index 84fc44e79..69b243108 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId "ceui.lisa.pixiv" minSdkVersion 21 targetSdkVersion 34 - versionCode 300 - versionName "4.1.5" + versionCode 324 + versionName "4.2.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions { @@ -81,13 +81,13 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.constraintlayout:constraintlayout:2.2.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'com.google.android.material:material:1.12.0' //=============== lifecycle - def lifecycle_version = '2.8.4' + def lifecycle_version = '2.8.7' implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" @@ -95,7 +95,7 @@ dependencies { //=============== Navigation - def nav_version = "2.7.7" + def nav_version = "2.8.4" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" @@ -114,14 +114,12 @@ dependencies { kapt(project(":processor")) // 3 implementation 'com.github.bumptech.glide:glide:4.16.0' + implementation 'com.github.bumptech.glide:okhttp3-integration:4.16.0' kapt 'com.github.bumptech.glide:compiler:4.16.0' - implementation ('com.github.bumptech.glide:okhttp3-integration:4.16.0'){ - exclude group: 'glide-parent' - } implementation 'com.github.lzyzsd:circleprogress:1.2.1' - def refresh_version = "2.0.6" + def refresh_version = "2.1.0" implementation "io.github.scwang90:refresh-layout-kernel:$refresh_version" //核心必须依赖 implementation "io.github.scwang90:refresh-header-classics:$refresh_version" //经典刷新头 implementation "io.github.scwang90:refresh-header-falsify:$refresh_version" //虚拟刷新头 @@ -150,8 +148,8 @@ dependencies { implementation 'com.github.skydoves:transformationlayout:1.1.1' implementation 'com.blankj:utilcodex:1.31.1' implementation 'com.safframework.log:saf-logginginterceptor:1.6.0' - implementation 'com.google.firebase:firebase-analytics:22.1.0' - implementation 'com.google.firebase:firebase-crashlytics:19.0.3' + implementation 'com.google.firebase:firebase-analytics:22.1.2' + implementation 'com.google.firebase:firebase-crashlytics:19.2.1' implementation 'com.afollestad:drag-select-recyclerview:2.4.0' @@ -175,7 +173,7 @@ dependencies { implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' implementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.1' - def lottieVersion = "3.4.0" + def lottieVersion = "3.4.1" implementation "com.airbnb.android:lottie:$lottieVersion" implementation 'xyz.zpayh:hdimageview:3.0.2' @@ -187,29 +185,27 @@ dependencies { implementation 'com.tencent:mmkv-static:1.3.9' implementation 'com.github.tbruyelle:rxpermissions:0.12' - implementation 'androidx.webkit:webkit:1.11.0' + implementation 'androidx.webkit:webkit:1.12.1' implementation 'org.jsoup:jsoup:1.18.1' implementation 'com.hjq:toast:8.8' + implementation 'com.jakewharton.timber:timber:5.0.1' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:runner:1.4.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation "androidx.documentfile:documentfile:1.0.1" implementation 'com.billy.android:smart-swipe:1.1.2' implementation 'org.honorato.multistatetogglebutton:multistatetogglebutton:0.2.2' implementation "io.noties.markwon:core:4.6.2" // Java language implementation - implementation("androidx.activity:activity-ktx:1.9.1") + implementation("androidx.activity:activity-ktx:1.9.3") implementation("com.google.android.flexbox:flexbox:3.0.0") - implementation("io.github.panpf.zoomimage:zoomimage-view-sketch:1.1.0-alpha05") + implementation("io.github.panpf.zoomimage:zoomimage-view-sketch4:1.1.0-rc03") implementation("me.zhanghai.android.fastscroll:library:1.2.0") - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.3' configurations { all*.exclude group: 'com.android.support', module: 'support-v13' diff --git a/app/src/main/java/ceui/lisa/activities/MainActivity.java b/app/src/main/java/ceui/lisa/activities/MainActivity.java index 2968f666b..b4816121a 100644 --- a/app/src/main/java/ceui/lisa/activities/MainActivity.java +++ b/app/src/main/java/ceui/lisa/activities/MainActivity.java @@ -80,7 +80,7 @@ public boolean hideStatusBar() { @Override protected void initView() { - Dev.isDev = Shaft.getMMKV().decodeBool(Params.USE_DEBUG, false); + Dev.isDev = false; baseBind.drawerLayout.setScrimColor(Color.TRANSPARENT); baseBind.navView.setNavigationItemSelectedListener(this); userHead = baseBind.navView.getHeaderView(0).findViewById(R.id.user_head); @@ -227,6 +227,7 @@ protected void initData() { } if (Dev.isDev) { startActivity(new Intent(this, HomeActivity.class)); + finish(); } else { if (Common.isAndroidQ()) { initFragment(); diff --git a/app/src/main/java/ceui/lisa/activities/Shaft.java b/app/src/main/java/ceui/lisa/activities/Shaft.java index 355d5f623..be649aca8 100644 --- a/app/src/main/java/ceui/lisa/activities/Shaft.java +++ b/app/src/main/java/ceui/lisa/activities/Shaft.java @@ -22,6 +22,8 @@ import com.tencent.mmkv.MMKV; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import ceui.lisa.R; import ceui.lisa.feature.HostManager; import ceui.lisa.helper.ShortcutHelper; @@ -37,6 +39,8 @@ import ceui.pixiv.session.SessionManager; import me.jessyan.progressmanager.ProgressManager; import okhttp3.OkHttpClient; +import timber.log.Timber; +import timber.log.Timber.DebugTree; import static ceui.lisa.utils.Local.LOCAL_DATA; /** @@ -90,6 +94,8 @@ public void onCreate() { sPreferences = getSharedPreferences(LOCAL_DATA, Context.MODE_PRIVATE); + Timber.plant(new Timber.DebugTree()); + MMKV.initialize(this); sUserModel = Local.getUser(); @@ -182,6 +188,23 @@ private void updateTheme() { } } + public static String getThemeColor() { + int current = Shaft.sSettings.getThemeIndex(); + return switch (current) { + case 0 -> "#686bdd"; + case 1 -> "#56baec"; + case 2 -> "#008BF3"; + case 3 -> "#03d0bf"; + case 4 -> "#fee65e"; + case 5 -> "#fe83a2"; + case 6 -> "#F44336"; + case 7 -> "#673AB7"; + case 8 -> "#4CAF50"; + case 9 -> "#E91E63"; + default -> "#686bdd"; + }; + } + @Override public void unbindService(ServiceConnection conn) { try { diff --git a/app/src/main/java/ceui/lisa/activities/UActivity.kt b/app/src/main/java/ceui/lisa/activities/UActivity.kt index 1645c7881..6e17094ee 100644 --- a/app/src/main/java/ceui/lisa/activities/UActivity.kt +++ b/app/src/main/java/ceui/lisa/activities/UActivity.kt @@ -41,6 +41,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import timber.log.Timber class UActivity : BaseActivity(), Display { private var userId = 0 @@ -291,7 +292,7 @@ fun FragmentActivity.unfollowUser(sender: ProgressTextButton, userId: Int) { ObjectPool.unFollowUser(userId.toLong()) Common.showToast(getString(R.string.cancel_like)) } catch (ex: Exception) { - ex.printStackTrace() + Timber.e(ex) Common.showToast(ex.message) } finally { sender.hideProgress() diff --git a/app/src/main/java/ceui/lisa/feature/HostManager.java b/app/src/main/java/ceui/lisa/feature/HostManager.java index e47d316f9..d8ed16991 100644 --- a/app/src/main/java/ceui/lisa/feature/HostManager.java +++ b/app/src/main/java/ceui/lisa/feature/HostManager.java @@ -35,13 +35,8 @@ private static class SingletonHolder { } public void init() { - if (Dev.isDev) { - host = "210.140.139.132"; - updateHost(); - } else { - host = randomHost(); - updateHost(); - } + host = randomHost(); + updateHost(); } /** @@ -63,6 +58,7 @@ private String randomHost() { "210.140.139.138" }; return already[Common.flatRandom(already.length)]; + } private void updateHost() { diff --git a/app/src/main/java/ceui/lisa/fragments/FragmentImageDetail.kt b/app/src/main/java/ceui/lisa/fragments/FragmentImageDetail.kt index a2df77e0b..c4e0690fb 100644 --- a/app/src/main/java/ceui/lisa/fragments/FragmentImageDetail.kt +++ b/app/src/main/java/ceui/lisa/fragments/FragmentImageDetail.kt @@ -4,23 +4,18 @@ import android.os.Bundle import android.text.TextUtils import android.view.View import androidx.fragment.app.viewModels -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.lifecycleScope import ceui.lisa.R import ceui.lisa.activities.Shaft import ceui.lisa.databinding.FragmentImageDetailBinding import ceui.lisa.download.IllustDownload import ceui.lisa.models.IllustsBean import ceui.lisa.utils.Params -import ceui.pixiv.ui.common.setUpFullScreen import ceui.pixiv.ui.common.setUpWithTaskStatus import ceui.pixiv.ui.task.NamedUrl import ceui.pixiv.ui.task.TaskPool import ceui.pixiv.ui.works.ToggleToolnarViewModel import ceui.refactor.setOnClick import com.github.panpf.sketch.loadImage -import kotlinx.coroutines.launch class FragmentImageDetail : BaseFragment() { private var mIllustsBean: IllustsBean? = null @@ -64,7 +59,7 @@ class FragmentImageDetail : BaseFragment() { if (imageUrl?.isNotEmpty() == true) { val task = TaskPool.getLoadTask(NamedUrl("", imageUrl), requireActivity()) - task.file.observe(viewLifecycleOwner) { file -> + task.result.observe(viewLifecycleOwner) { file -> baseBind.image.loadImage(file) } baseBind.progressCircular.setUpWithTaskStatus(task.status, viewLifecycleOwner) diff --git a/app/src/main/java/ceui/lisa/fragments/FragmentLogin.kt b/app/src/main/java/ceui/lisa/fragments/FragmentLogin.kt index 53a0af93d..d3ebf7247 100644 --- a/app/src/main/java/ceui/lisa/fragments/FragmentLogin.kt +++ b/app/src/main/java/ceui/lisa/fragments/FragmentLogin.kt @@ -66,44 +66,19 @@ class FragmentLogin : BaseFragment() { startActivity(intent) return@OnMenuItemClickListener true } else if (item.itemId == R.id.action_import) { - if (Dev.isDev) { - performLogin(SAMPLE_USER_TOKEN) + val userJson = ClipBoardUtils.getClipboardContent(mContext) + if (userJson != null && !TextUtils.isEmpty(userJson) + && userJson.contains(Params.USER_KEY) + ) { + performLogin(userJson) } else { - val userJson = ClipBoardUtils.getClipboardContent(mContext) - if (userJson != null && !TextUtils.isEmpty(userJson) - && userJson.contains(Params.USER_KEY) - ) { - performLogin(userJson) - } else { - Common.showToast("剪贴板无用户信息", 3) - } + Common.showToast("剪贴板无用户信息", 3) } return@OnMenuItemClickListener true } false }) - setTitle() - baseBind.title.setOnClickListener { - if (mHitCountDown > 0) { - mHitCountDown-- - if (mHitCountDown == 0) { - showDialog() - } else if (mHitCountDown > 0 && mHitCountDown < TAPS_TO_BE_A_DEVELOPER - 2) { - if (mHitToast != null) { - mHitToast?.cancel() - } - mHitToast = Toast.makeText( - mActivity, String.format( - Locale.getDefault(), - "点击%d次切换版本", mHitCountDown - ), Toast.LENGTH_SHORT - ) - mHitToast?.show() - } - } else { - showDialog() - } - } + baseBind.title.text = getString(R.string.app_name) baseBind.login.setOnClickListener { checkAndNext { openProxyHint { @@ -195,32 +170,6 @@ class FragmentLogin : BaseFragment() { qmuiDialog.show() } - private fun setTitle() { - if (Shaft.getMMKV().decodeBool(Params.USE_DEBUG, false)) { - baseBind.title.text = "Shaft(测试版)" - } else { - baseBind.title.text = "Shaft" - } - } - - private fun showDialog() { - val builder = AlertDialog.Builder(mContext) - val titles = arrayOf("使用正式版", "使用测试版") - builder.setItems(titles) { dialog, which -> - if (which == 0) { - Shaft.getMMKV().encode(Params.USE_DEBUG, false) - Dev.isDev = false - } else if (which == 1) { - Shaft.getMMKV().encode(Params.USE_DEBUG, true) - Dev.isDev = true - } - mHitCountDown = TAPS_TO_BE_A_DEVELOPER - setTitle() - } - val alertDialog = builder.create() - alertDialog.show() - } - override fun initData() { if (Shaft.getMMKV().decodeBool(Params.SHOW_DIALOG, true)) { Common.createDialog(mContext) @@ -315,48 +264,3 @@ fun SpannableString.setLinkSpan(text: String, hideUnderLine: Boolean = true, col ) } } - -private const val SAMPLE_USER_TOKEN = """{ - "access_token": "kj_cDCmlXxdvRkUQ8LfgfyQoprxq6MEBDAW5A41uqb0", - "expires_in": 3600, - "token_type": "bearer", - "scope": "", - "refresh_token": "C6tnl4ByWwOkfmckPJsJPf_Ra-MO_-TW_Q1bH1g2a38", - "user": { - "profile_image_urls": { - "px_16x16": "https:\/\/i.pximg.net\/user-profile\/img\/2018\/12\/22\/16\/00\/30\/15159182_1e25c44944b0130e7fc9cbf22db96844_16.jpg", - "px_50x50": "https:\/\/i.pximg.net\/user-profile\/img\/2018\/12\/22\/16\/00\/30\/15159182_1e25c44944b0130e7fc9cbf22db96844_50.jpg", - "px_170x170": "https:\/\/i.pximg.net\/user-profile\/img\/2018\/12\/22\/16\/00\/30\/15159182_1e25c44944b0130e7fc9cbf22db96844_170.jpg" - }, - "id": "31660292", - "name": "meppoi", - "account": "meppoi", - "mail_address": "863043461@qq.com", - "is_premium": false, - "x_restrict": 0, - "is_mail_authorized": true, - "require_policy_agreement": false - }, - "response": { - "access_token": "kj_cDCmlXxdvRkUQ8LfgfyQoprxq6MEBDAW5A41uqb0", - "expires_in": 3600, - "token_type": "bearer", - "scope": "", - "refresh_token": "C6tnl4ByWwOkfmckPJsJPf_Ra-MO_-TW_Q1bH1g2a38", - "user": { - "profile_image_urls": { - "px_16x16": "https:\/\/i.pximg.net\/user-profile\/img\/2018\/12\/22\/16\/00\/30\/15159182_1e25c44944b0130e7fc9cbf22db96844_16.jpg", - "px_50x50": "https:\/\/i.pximg.net\/user-profile\/img\/2018\/12\/22\/16\/00\/30\/15159182_1e25c44944b0130e7fc9cbf22db96844_50.jpg", - "px_170x170": "https:\/\/i.pximg.net\/user-profile\/img\/2018\/12\/22\/16\/00\/30\/15159182_1e25c44944b0130e7fc9cbf22db96844_170.jpg" - }, - "id": "31660292", - "name": "meppoi", - "account": "meppoi", - "mail_address": "863043461@qq.com", - "is_premium": false, - "x_restrict": 0, - "is_mail_authorized": true, - "require_policy_agreement": false - } - } -}""" \ No newline at end of file diff --git a/app/src/main/java/ceui/lisa/fragments/FragmentNovelHolder.java b/app/src/main/java/ceui/lisa/fragments/FragmentNovelHolder.java index df4d79ddb..b50fd5924 100644 --- a/app/src/main/java/ceui/lisa/fragments/FragmentNovelHolder.java +++ b/app/src/main/java/ceui/lisa/fragments/FragmentNovelHolder.java @@ -51,9 +51,11 @@ import ceui.lisa.models.NovelSearchResponse; import ceui.lisa.models.TagsBean; import ceui.lisa.utils.Common; +import ceui.lisa.utils.DensityUtil; import ceui.lisa.utils.GlideUtil; import ceui.lisa.utils.Params; import ceui.lisa.utils.PixivOperate; +import ceui.lisa.view.LinearItemDecoration; import ceui.lisa.view.ScrollChange; import ceui.loxia.SpaceHolder; import ceui.loxia.TextDescHolder; @@ -445,9 +447,11 @@ private void setNovelAdapter() { holderList.add(new TextDescHolder(getString(R.string.string_107))); holderList.add(new SpaceHolder()); CommonAdapter commonAdapter = new CommonAdapter(getViewLifecycleOwner()); + baseBind.viewPager.addItemDecoration(new LinearItemDecoration(DensityUtil.dp2px(16))); baseBind.viewPager.setAdapter(commonAdapter); commonAdapter.submitList(holderList); } else { + baseBind.viewPager.addItemDecoration(new LinearItemDecoration(DensityUtil.dp2px(16))); baseBind.viewPager.setAdapter(new VNewAdapter(novelDetail.getParsedChapters(), mContext)); } if(novelDetail.getNovel_marker() != null){ diff --git a/app/src/main/java/ceui/lisa/fragments/FragmentRecmdIllust.java b/app/src/main/java/ceui/lisa/fragments/FragmentRecmdIllust.java index 0622c7c85..801367cb4 100644 --- a/app/src/main/java/ceui/lisa/fragments/FragmentRecmdIllust.java +++ b/app/src/main/java/ceui/lisa/fragments/FragmentRecmdIllust.java @@ -72,17 +72,7 @@ public Class> modelClass() { @Override public RemoteRepo repository() { - if (Dev.isDev) { - localData = AppDatabase.getAppDatabase(mContext).recmdDao().getAll(); - return new RecmdIllustRepo(dataType) { - @Override - public boolean localData() { - return !Common.isEmpty(localData); - } - }; - } else { - return new RecmdIllustRepo(dataType); - } + return new RecmdIllustRepo(dataType); } @Override diff --git a/app/src/main/java/ceui/lisa/repo/PivisionRepo.kt b/app/src/main/java/ceui/lisa/repo/PivisionRepo.kt index 0619fc49e..b17e5b8a7 100644 --- a/app/src/main/java/ceui/lisa/repo/PivisionRepo.kt +++ b/app/src/main/java/ceui/lisa/repo/PivisionRepo.kt @@ -21,8 +21,4 @@ open class PivisionRepo( } return Retro.getAppApi().getNextArticles(token(), nextUrl) } - - override fun localData(): Boolean { - return Dev.isDev - } } diff --git a/app/src/main/java/ceui/lisa/repo/RecmdUserRepo.kt b/app/src/main/java/ceui/lisa/repo/RecmdUserRepo.kt index 9f2d44b2c..4958f7f30 100644 --- a/app/src/main/java/ceui/lisa/repo/RecmdUserRepo.kt +++ b/app/src/main/java/ceui/lisa/repo/RecmdUserRepo.kt @@ -18,11 +18,4 @@ class RecmdUserRepo(private val isHorizontal: Boolean) : RemoteRepo() } return Retro.getAppApi().getNextUser(token(), nextUrl) } - - override fun localData(): Boolean { - if (isHorizontal) { - return Dev.isDev - } - return super.localData() - } } diff --git a/app/src/main/java/ceui/lisa/repo/RightRepo.kt b/app/src/main/java/ceui/lisa/repo/RightRepo.kt index ee881ea27..c30df73b0 100644 --- a/app/src/main/java/ceui/lisa/repo/RightRepo.kt +++ b/app/src/main/java/ceui/lisa/repo/RightRepo.kt @@ -36,8 +36,4 @@ class RightRepo(var restrict: String?) : RemoteRepo() { override fun mapper(): Function { return FilterMapper() } - - override fun localData(): Boolean { - return Dev.isDev - } } diff --git a/app/src/main/java/ceui/lisa/utils/Dev.java b/app/src/main/java/ceui/lisa/utils/Dev.java index 4c7787f20..84bcc234b 100644 --- a/app/src/main/java/ceui/lisa/utils/Dev.java +++ b/app/src/main/java/ceui/lisa/utils/Dev.java @@ -3,7 +3,7 @@ public class Dev { //是否是开发状态 - public static boolean isDev = true; + public static boolean isDev = false; public static boolean refreshUser = false; public static boolean hideMainActivityStatus = true; diff --git a/app/src/main/java/ceui/loxia/API.kt b/app/src/main/java/ceui/loxia/API.kt index 0b1844120..0c4eb0407 100644 --- a/app/src/main/java/ceui/loxia/API.kt +++ b/app/src/main/java/ceui/loxia/API.kt @@ -54,6 +54,11 @@ interface API { @Query("illust_id") illust_id: Long ): SingleIllustResponse + @GET("/v2/illust/related") + suspend fun getRelatedIllusts( + @Query("illust_id") illust_id: Long, + ): IllustResponse + @GET("/v1/walkthrough/illusts") suspend fun getWalkthroughWorks(): IllustResponse diff --git a/app/src/main/java/ceui/loxia/Models.kt b/app/src/main/java/ceui/loxia/Models.kt index 76c109ad7..9e98fd231 100644 --- a/app/src/main/java/ceui/loxia/Models.kt +++ b/app/src/main/java/ceui/loxia/Models.kt @@ -88,7 +88,6 @@ data class WebIllust( val url_s: String? = null, val urls: Map? = null, val userId: Long = 0L, - val tags: List? = null, val userName: String? = null, val width: Int, val xRestrict: Int? = null, @@ -115,7 +114,6 @@ data class WebIllust( restrict = restrict, sanity_level = sl, series = null, - tags = tags?.map { Tag(name = it) }, title = title, tools = null, total_bookmarks = null, diff --git a/app/src/main/java/ceui/loxia/ProgressButton.kt b/app/src/main/java/ceui/loxia/ProgressButton.kt index 2d7b0c022..2a8c33016 100644 --- a/app/src/main/java/ceui/loxia/ProgressButton.kt +++ b/app/src/main/java/ceui/loxia/ProgressButton.kt @@ -8,16 +8,14 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.Gravity +import androidx.core.content.res.ResourcesCompat import androidx.swiperefreshlayout.widget.CircularProgressDrawable import ceui.lisa.R import kotlin.math.roundToInt -interface Progressable { - var isProgressing: Boolean -} class ProgressImageButton(context: Context, attrs: AttributeSet?, defStyle: Int) : - androidx.appcompat.widget.AppCompatImageButton(context, attrs, defStyle), Progressable { + androidx.appcompat.widget.AppCompatImageButton(context, attrs, defStyle), ProgressIndicator { data class OriginalState( val drawable: Drawable?, @@ -28,47 +26,13 @@ class ProgressImageButton(context: Context, attrs: AttributeSet?, defStyle: Int) var preferSize: Int? = null - override var isProgressing: Boolean = false - set(value) { - if (value == field) { - return - } - - if (value) { - originalState = OriginalState(drawable, isClickable) - - val progressDrawable = CircularProgressDrawable(context).apply { - setColorSchemeColors(Color.WHITE) - strokeCap = Paint.Cap.ROUND - strokeWidth = progressStrokeWidth - centerRadius = (preferSize?.toFloat()?.div(2)) ?: (progressWidth / 2) - } - - progressDrawable.start() - - isClickable = false - setImageDrawable(progressDrawable) - - - } else { - (drawable as? CircularProgressDrawable)?.stop() - - require(originalState != null) - originalState?.let { - isClickable = it.isClickable - setImageDrawable(it.drawable) - } - } - field = value - } - - fun showProgress(progress: Boolean) { - isProgressing = progress - } private val progressStrokeWidth: Float private val progressWidth: Float + private var isAnimationRunning = false + private var pendingTarget: Drawable? = null + constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) @@ -90,11 +54,48 @@ class ProgressImageButton(context: Context, attrs: AttributeSet?, defStyle: Int) AnimatorInflater.loadStateListAnimator(context, R.animator.button_press_alpha) } + override fun setImageResource(resId: Int) { + if (isAnimationRunning) { + pendingTarget = ResourcesCompat.getDrawable(resources, resId, context.theme) + } else { + super.setImageResource(resId) + } + } + + override fun showProgress() { + originalState = OriginalState(drawable, isClickable) + + val progressDrawable = CircularProgressDrawable(context).apply { + setColorSchemeColors(Color.WHITE) + strokeCap = Paint.Cap.ROUND + strokeWidth = progressStrokeWidth + centerRadius = (preferSize?.toFloat()?.div(2)) ?: (progressWidth / 2) + } + + progressDrawable.start() + + isClickable = false + setImageDrawable(progressDrawable) + isAnimationRunning = true + } + + override fun hideProgress() { + isAnimationRunning = false + (drawable as? CircularProgressDrawable)?.stop() + + require(originalState != null) + originalState?.let { + isClickable = it.isClickable + setImageDrawable(pendingTarget ?: it.drawable) + pendingTarget = null + } + } + } + class ProgressTextButton(context: Context, attrs: AttributeSet?, defStyle: Int) : - androidx.appcompat.widget.AppCompatButton(context, attrs, defStyle), Drawable.Callback, - Progressable { + androidx.appcompat.widget.AppCompatButton(context, attrs, defStyle), Drawable.Callback, ProgressIndicator { data class OriginalState( val padding: Rect, @@ -105,54 +106,9 @@ class ProgressTextButton(context: Context, attrs: AttributeSet?, defStyle: Int) private var originalState: OriginalState? = null - override var isProgressing: Boolean = false - set(value) { - if (value == field) { - return - } - - if (value) { - originalState = OriginalState( - Rect(paddingLeft, paddingTop, paddingRight, paddingBottom), - text.toString(), - isClickable, - compoundDrawables.firstOrNull() - ) - - val circleWidth = progressWidth.roundToInt() - val circleHeight = progressWidth.roundToInt() - - val hPadding = (width - circleWidth) / 2 - val vPadding = (height - circleHeight - paddingTop - paddingBottom) / 2 - - isClickable = false - text = null - - val drawable = CircularProgressDrawable(context).apply { - setColorSchemeColors(Color.WHITE) - strokeCap = Paint.Cap.ROUND - strokeWidth = progressStrokeWidth - centerRadius = progressWidth / 2 - } - - setPadding(hPadding, vPadding, hPadding, vPadding) - drawable.bounds = Rect(0, 0, circleWidth, circleHeight) - - drawable.callback = this - setCompoundDrawables(drawable, null, null, null) - drawable.start() - } else { - require(originalState != null) - setCompoundDrawables(originalState?.drawable, null, null, null) - originalState?.let { - text = it.text - isClickable = it.isClickable - setPadding(it.padding.left, it.padding.top, it.padding.right, it.padding.bottom) - } - } - - field = value - } + private var pendingTarget: String? = null + private var color: Int = Color.WHITE + private val progressStrokeWidth: Float private val progressWidth: Float @@ -162,6 +118,7 @@ class ProgressTextButton(context: Context, attrs: AttributeSet?, defStyle: Int) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) init { + val ta = context.obtainStyledAttributes(attrs, R.styleable.ProgressTextButton) progressStrokeWidth = ta.getDimension( R.styleable.ProgressTextButton_ptb_progress_stroke_width, @@ -178,12 +135,64 @@ class ProgressTextButton(context: Context, attrs: AttributeSet?, defStyle: Int) AnimatorInflater.loadStateListAnimator(context, R.animator.button_press_alpha) } - fun showProgress() { - isProgressing = true + private var isAnimationRunning = false + + override fun showProgress() { + originalState = OriginalState( + Rect(paddingLeft, paddingTop, paddingRight, paddingBottom), + text.toString(), + isClickable, + compoundDrawables.firstOrNull() + ) + + val circleWidth = progressWidth.roundToInt() + val circleHeight = progressWidth.roundToInt() + + val hPadding = (width - circleWidth) / 2 + (height - circleHeight - paddingTop - paddingBottom) / 2 + + isClickable = false + + val drawable = CircularProgressDrawable(context).apply { + setColorSchemeColors(color) + strokeCap = Paint.Cap.ROUND + strokeWidth = progressStrokeWidth + centerRadius = progressWidth / 2 + } + + text = null + setPadding(hPadding, 0, hPadding, 0) + drawable.bounds = Rect(0, 0, circleWidth, circleHeight) + drawable.callback = this + setCompoundDrawables(drawable, null, null, null) + drawable.start() + isAnimationRunning = true } - fun hideProgress() { - isProgressing = false + + override fun hideProgress() { + isAnimationRunning = false + val stored = originalState?.copy() ?: return + originalState = null // 清空状态 + // 恢复原始状态 + setCompoundDrawables(stored.drawable, null, null, null) + text = pendingTarget ?: stored.text // 恢复文字 + pendingTarget = null + isClickable = stored.isClickable + setPadding( + stored.padding.left, + stored.padding.top, + stored.padding.right, + stored.padding.bottom + ) + } + + override fun setText(text: CharSequence?, type: BufferType?) { + if (isAnimationRunning) { + pendingTarget = text?.toString() + } else { + super.setText(text, type) + } } override fun invalidateDrawable(who: Drawable) { diff --git a/app/src/main/java/ceui/loxia/ProgressIndicator.kt b/app/src/main/java/ceui/loxia/ProgressIndicator.kt new file mode 100644 index 000000000..9ca5cb36b --- /dev/null +++ b/app/src/main/java/ceui/loxia/ProgressIndicator.kt @@ -0,0 +1,8 @@ +package ceui.loxia + +interface ProgressIndicator { + + fun showProgress() + + fun hideProgress() +} \ No newline at end of file diff --git a/app/src/main/java/ceui/loxia/RefreshState.kt b/app/src/main/java/ceui/loxia/RefreshState.kt index 31bb6331b..299fdb8c3 100644 --- a/app/src/main/java/ceui/loxia/RefreshState.kt +++ b/app/src/main/java/ceui/loxia/RefreshState.kt @@ -79,7 +79,7 @@ fun ItemLoadingBinding.setUpRefreshState( } refreshState.observe(viewLifecycleOwner) { refreshState -> if (refreshState is RefreshState.LOADED) { - progressCircular.showProgress(false) + progressCircular.hideProgress() loadingFrame.isVisible = false refreshLayout.finishRefresh() refreshLayout.finishLoadMore() @@ -101,16 +101,16 @@ fun ItemLoadingBinding.setUpRefreshState( emptyFrame.isVisible = false if (refreshState.refreshHint == RefreshHint.PullToRefresh) { loadingFrame.isVisible = false - progressCircular.showProgress(false) + progressCircular.hideProgress() if (!refreshLayout.isRefreshing) { refreshLayout.autoRefreshAnimationOnly() } } else if (refreshState.refreshHint == RefreshHint.InitialLoad) { loadingFrame.isVisible = true - progressCircular.showProgress(true) + progressCircular.showProgress() } } else if (refreshState is RefreshState.ERROR) { - progressCircular.showProgress(false) + progressCircular.hideProgress() loadingFrame.isVisible = false refreshLayout.finishRefresh() refreshLayout.finishLoadMore() @@ -124,7 +124,7 @@ fun ItemLoadingBinding.setUpRefreshState( } fun Throwable.getHumanReadableMessage(context: Context): String { - return if (this is UnknownHostException || this is SSLHandshakeException || this is TimeoutException || this is SocketTimeoutException) { + return if (this is SSLHandshakeException || this is TimeoutException || this is SocketTimeoutException) { "${context.getString(R.string.connection_error)}: ${this.javaClass.simpleName}" } else { val lc = localizedMessage diff --git a/app/src/main/java/ceui/loxia/SlinkyFooter.kt b/app/src/main/java/ceui/loxia/SlinkyFooter.kt index 5c4029008..d163b5dda 100644 --- a/app/src/main/java/ceui/loxia/SlinkyFooter.kt +++ b/app/src/main/java/ceui/loxia/SlinkyFooter.kt @@ -18,7 +18,7 @@ class SlinkyFooter(context: Context, attrs: AttributeSet? = null) : ClassicsFoot loadingFrame.updateLayoutParams { height = 100.ppppx } - progressCircular.showProgress(true) + progressCircular.showProgress() } override fun getView(): View { diff --git a/app/src/main/java/ceui/loxia/UIAction.kt b/app/src/main/java/ceui/loxia/UIAction.kt index 0e81c9e09..cf911f0d7 100644 --- a/app/src/main/java/ceui/loxia/UIAction.kt +++ b/app/src/main/java/ceui/loxia/UIAction.kt @@ -16,14 +16,12 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView import ceui.lisa.R import ceui.lisa.utils.Common -import ceui.pixiv.ui.common.FragmentResultRequestIdOwner -import ceui.pixiv.widgets.FragmentResultByFragment -import ceui.pixiv.widgets.FragmentResultStore import ceui.pixiv.widgets.PixivDialog import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import timber.log.Timber import java.util.UUID inline fun Fragment.sendAction(action: (receiver: InterfaceT) -> Boolean) { @@ -57,36 +55,24 @@ fun Fragment.launchSuspend(block: suspend CoroutineScope.() -> Unit) { try { block() } catch (ex: Exception) { - ex.printStackTrace() + Timber.e(ex) } } } -fun Fragment.launchSuspend(sender: ProgressTextButton, block: suspend CoroutineScope.() -> Unit) { +fun Fragment.launchSuspend(sender: ProgressIndicator, block: suspend CoroutineScope.() -> Unit) { viewLifecycleOwnerLiveData.value?.lifecycleScope?.launch { try { sender.showProgress() block() } catch (ex: Exception) { - ex.printStackTrace() + Timber.e(ex) } finally { sender.hideProgress() } } } -fun Fragment.launchSuspend(sender: ProgressImageButton, block: suspend CoroutineScope.() -> Unit) { - viewLifecycleOwnerLiveData.value?.lifecycleScope?.launch { - try { - sender.showProgress(true) - block() - } catch (ex: Exception) { - ex.printStackTrace() - } finally { - sender.showProgress(false) - } - } -} fun NavOptions.Builder.setHorizontalSlide(): NavOptions.Builder { return setEnterAnim(R.anim.h_slide_enter) @@ -110,87 +96,7 @@ fun NavOptions.Builder.setFadeIn(): NavOptions.Builder { .setPopExitAnim(R.anim.slow_fade_out) } -const val FRAGMENT_RESULT_REQUEST_ID = "FragmentResultRequestId" - - -internal fun T.listenToResultStore(resultStore: FragmentResultStore) where T: Fragment, T: FragmentResultRequestIdOwner { - val fragment = this - val uniqueId = fragmentUniqueId - viewLifecycleOwner.lifecycleScope.launchWhenResumed { - resultStore.getTypedResult(uniqueId)?.let { result -> - resultStore.getTypedTask(uniqueId)?.let { task -> - task.complete(FragmentResultByFragment(result, fragment)) - resultStore.removeResult(uniqueId) - } - } - } -} - -inline fun FragmentT.pushFragmentForResult( - id: Int, - bundle: Bundle? = null, - crossinline onResult: FragmentT.(T) -> Unit -) where FragmentT : Fragment, FragmentT : FragmentResultRequestIdOwner { - val caller = this - Common.showLog("dsaasdw gogogogo ${caller.fragmentUniqueId} picked launchWhenResumed use ${lifecycle.currentState}") - - val fragmentResultStore by activityViewModels() - val requestId = fragmentUniqueId - val task = CompletableDeferred>() - fragmentResultStore.putTask(requestId, task as CompletableDeferred>) - - // 如果 bundle 为 null,则创建一个新的 Bundle - val args = bundle ?: Bundle() - // 将 requestId 添加到 Bundle 中 - args.putString(FRAGMENT_RESULT_REQUEST_ID, requestId) - MainScope().launch { - val result = task.await() - result.fragment.onResult(result.result) - } - findNavController().navigate( - id, - args, - NavOptions.Builder().setHorizontalSlide().build() - ) -} - - -inline fun FragmentT.promptFragmentForResult( - dialogProducer: () -> DialogFragment, - bundle: Bundle? = null, - crossinline onResult: FragmentT.(T) -> Unit -) where FragmentT : Fragment, FragmentT : FragmentResultRequestIdOwner { - val caller = this - Common.showLog("dsaasdw gogogogo ${caller.fragmentUniqueId} picked launchWhenResumed use ${lifecycle.currentState}") - - val fragmentResultStore by activityViewModels() - val requestId = fragmentUniqueId - val task = CompletableDeferred>() - fragmentResultStore.putTask(requestId, task as CompletableDeferred>) - fragmentResultStore.registerListener(requestId, lifecycle) - lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { - super.onDestroy(owner) - fragmentResultStore.unRegisterListener(requestId) - } - }) - - // 如果 bundle 为 null,则创建一个新的 Bundle - val args = bundle ?: Bundle() - // 将 requestId 添加到 Bundle 中 - args.putString(FRAGMENT_RESULT_REQUEST_ID, requestId) - - MainScope().launch { - val result = task.await() - result.fragment.onResult(result.result) - } - val dialogFragment = dialogProducer() - dialogFragment.apply { - arguments = args - } - dialogFragment.show(childFragmentManager, "Tag") -} fun Fragment.pushFragment(id: Int, bundle: Bundle? = null) { findNavController().navigate( diff --git a/app/src/main/java/ceui/loxia/WebHeaderInterceptor.kt b/app/src/main/java/ceui/loxia/WebHeaderInterceptor.kt index f55f1ae42..49f999646 100644 --- a/app/src/main/java/ceui/loxia/WebHeaderInterceptor.kt +++ b/app/src/main/java/ceui/loxia/WebHeaderInterceptor.kt @@ -1,6 +1,8 @@ package ceui.loxia import ceui.lisa.utils.Common +import ceui.pixiv.session.SessionManager +import ceui.pixiv.ui.settings.SettingsFragment import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.tencent.mmkv.MMKV @@ -25,7 +27,7 @@ class WebHeaderInterceptor : Interceptor { } private fun addHeader(before: Request.Builder): Request.Builder { - val cookies = prefStore.getString("web-api-cookie", "") ?: "" + val cookies = prefStore.getString(SessionManager.COOKIE_KEY, "") ?: "" // val end = cookies.substring(1, cookies.length - 1) // val end2 = end.replace("first_visit_datetime", "first_visit_datetime_pc") Common.showLog("dsaadsdsaaww2 get ${cookies}") diff --git a/app/src/main/java/ceui/loxia/flag/FlagReasonFragment.kt b/app/src/main/java/ceui/loxia/flag/FlagReasonFragment.kt index cba54c916..3df32eb71 100644 --- a/app/src/main/java/ceui/loxia/flag/FlagReasonFragment.kt +++ b/app/src/main/java/ceui/loxia/flag/FlagReasonFragment.kt @@ -8,6 +8,8 @@ import ceui.lisa.R import ceui.lisa.activities.TemplateActivity import ceui.lisa.databinding.FragmentSlinkyListBinding import ceui.loxia.* +import ceui.pixiv.ui.common.ListMode +import ceui.pixiv.ui.common.setUpLayoutManager import ceui.refactor.viewBinding class FlagReasonFragment : SlinkyListFragment(), FlagActionReceiver { @@ -23,7 +25,7 @@ class FlagReasonFragment : SlinkyListFragment(), FlagActionReceiver { val activity = requireActivity() binding.toolbar.toolbar.setNavigationOnClickListener { activity.finish() } binding.toolbar.toolbarTitle.text = getString(R.string.violated_rule) - binding.listView.layoutManager = LinearLayoutManager(activity) + setUpLayoutManager(binding.listView, ListMode.VERTICAL_NO_MARGIN) setUpSlinkyList(binding.listView, binding.refreshLayout, binding.itemLoading, viewModel) } diff --git a/app/src/main/java/ceui/pixiv/session/SessionManager.kt b/app/src/main/java/ceui/pixiv/session/SessionManager.kt index d221ea2fe..89842062e 100644 --- a/app/src/main/java/ceui/pixiv/session/SessionManager.kt +++ b/app/src/main/java/ceui/pixiv/session/SessionManager.kt @@ -3,25 +3,24 @@ package ceui.pixiv.session import android.text.TextUtils import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import ceui.lisa.fragments.FragmentLogin import ceui.lisa.models.UserModel -import ceui.lisa.utils.Local import ceui.loxia.AccountResponse import ceui.loxia.Client import ceui.loxia.Event import ceui.loxia.ObjectPool -import ceui.loxia.User import com.google.gson.Gson import com.tencent.mmkv.MMKV import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import timber.log.Timber object SessionManager { - private const val LoggedInUserJsonKey = "LoggedInUserJsonKey" + private const val USER_KEY = "LoggedInUserJsonKey" + const val COOKIE_KEY = "web-api-cookie" private val _loggedInAccount = MutableLiveData() private val gson = Gson() @@ -52,7 +51,7 @@ object SessionManager { } fun load() { - val json = prefStore.getString(LoggedInUserJsonKey, "") + val json = prefStore.getString(USER_KEY, "") if (json?.isNotEmpty() == true) { try { val accountResponse = gson.fromJson(json, AccountResponse::class.java) @@ -68,24 +67,24 @@ object SessionManager { fun updateSession(userModel: UserModel?) { if (userModel == null) { - prefStore.putString(LoggedInUserJsonKey, "") + prefStore.putString(USER_KEY, "") _loggedInAccount.value = AccountResponse() } else { val javaJson = gson.toJson(userModel) val accountResponse = gson.fromJson(javaJson, AccountResponse::class.java) - prefStore.putString(LoggedInUserJsonKey, gson.toJson(accountResponse)) + prefStore.putString(USER_KEY, gson.toJson(accountResponse)) _loggedInAccount.value = accountResponse } } fun postUpdateSession(userModel: UserModel?) { if (userModel == null) { - prefStore.putString(LoggedInUserJsonKey, "") + prefStore.putString(USER_KEY, "") _loggedInAccount.postValue(AccountResponse()) } else { val javaJson = gson.toJson(userModel) val accountResponse = gson.fromJson(javaJson, AccountResponse::class.java) - prefStore.putString(LoggedInUserJsonKey, gson.toJson(accountResponse)) + prefStore.putString(USER_KEY, gson.toJson(accountResponse)) _loggedInAccount.postValue(accountResponse) } } @@ -119,7 +118,7 @@ object SessionManager { throw RuntimeException("newRefreshToken failed") } } catch (ex: Exception) { - ex.printStackTrace() + Timber.e(ex) null } finally { _isRenewToken.postValue(false) diff --git a/app/src/main/java/ceui/pixiv/ui/article/ArticlesFragment.kt b/app/src/main/java/ceui/pixiv/ui/article/ArticlesFragment.kt index 30df9cd3f..a8d1ee6a6 100644 --- a/app/src/main/java/ceui/pixiv/ui/article/ArticlesFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/article/ArticlesFragment.kt @@ -9,6 +9,7 @@ import ceui.lisa.utils.Params import ceui.lisa.view.LinearItemDecoration import ceui.loxia.Client import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.PvisionCardHolder import ceui.pixiv.ui.common.setUpRefreshState @@ -28,9 +29,7 @@ class ArticlesFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) binding.toolbarLayout.naviTitle.text = getString(R.string.pixiv_special) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - binding.listView.addItemDecoration(LinearItemDecoration(20.ppppx)) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/bottom/ItemListDialogFragment.kt b/app/src/main/java/ceui/pixiv/ui/bottom/ItemListDialogFragment.kt index 8642aa921..8eb34410d 100644 --- a/app/src/main/java/ceui/pixiv/ui/bottom/ItemListDialogFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/bottom/ItemListDialogFragment.kt @@ -72,7 +72,6 @@ class ItemListDialogFragment : PixivBottomSheet(R.layout.fragment_item_list_dial } override fun onClickOffsetPage(index: Int) { - setFragmentResult(index) dismissAllowingStateLoss() } } diff --git a/app/src/main/java/ceui/pixiv/ui/chats/MyChatsFragment.kt b/app/src/main/java/ceui/pixiv/ui/chats/MyChatsFragment.kt index a38b84509..048bf4c6f 100644 --- a/app/src/main/java/ceui/pixiv/ui/chats/MyChatsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/chats/MyChatsFragment.kt @@ -11,6 +11,7 @@ import ceui.pixiv.ui.circles.SmartFragmentPagerAdapter import ceui.pixiv.ui.common.HomeTabContainer import ceui.pixiv.ui.common.TitledViewPagerFragment import ceui.pixiv.ui.home.WalkthroughFragment +import ceui.pixiv.ui.rank.RankPreviewFragment import ceui.pixiv.widgets.setUpWith import ceui.refactor.viewBinding @@ -22,6 +23,12 @@ class MyChatsFragment : TitledViewPagerFragment(R.layout.fragment_my_chats), Hom super.onViewCreated(view, savedInstanceState) val adapter = SmartFragmentPagerAdapter( listOf( + PagedFragmentItem( + builder = { + RankPreviewFragment() + }, + initialTitle = getString(R.string.ranking_list) + ), PagedFragmentItem( builder = { SquareFragment().apply { @@ -44,12 +51,12 @@ class MyChatsFragment : TitledViewPagerFragment(R.layout.fragment_my_chats), Hom }, initialTitle = "Pixivision" ), - PagedFragmentItem( - builder = { - WalkthroughFragment() - }, - initialTitle = "画廊" - ), +// PagedFragmentItem( +// builder = { +// WalkthroughFragment() +// }, +// initialTitle = "画廊" +// ), ), this ) diff --git a/app/src/main/java/ceui/pixiv/ui/chats/SquareFragment.kt b/app/src/main/java/ceui/pixiv/ui/chats/SquareFragment.kt index 8fd48f1ae..2a1f55bf9 100644 --- a/app/src/main/java/ceui/pixiv/ui/chats/SquareFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/chats/SquareFragment.kt @@ -17,50 +17,43 @@ import ceui.loxia.Client import ceui.loxia.SquareResponse import ceui.loxia.WebIllust import ceui.loxia.findActionReceiverOrNull +import ceui.pixiv.session.SessionManager import ceui.pixiv.ui.common.CommonAdapter import ceui.pixiv.ui.common.IllustCardActionReceiver import ceui.pixiv.ui.common.ListItemHolder import ceui.pixiv.ui.common.ListItemViewHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.ResponseStore +import ceui.pixiv.ui.common.createResponseStore import ceui.pixiv.ui.common.pixivValueViewModel import ceui.pixiv.ui.common.setUpRefreshState +import ceui.pixiv.ui.settings.CookieNotSyncException import ceui.refactor.ppppx import ceui.refactor.setOnClick import ceui.refactor.viewBinding import com.bumptech.glide.Glide +import com.tencent.mmkv.MMKV class SquareFragment : PixivFragment(R.layout.fragment_pixiv_list) { private val binding by viewBinding(FragmentPixivListBinding::bind) private val args by navArgs() - private val viewModel by pixivValueViewModel { - val responseStore = ResponseStore( - keyProvider = { "home-square-${args.objectType}" }, - expirationTimeMillis = 1800 * 1000L, - typeToken = SquareResponse::class.java, - dataLoader = { Client.webApi.getSquareContents(args.objectType) } - ) - responseStore.retrieveData() + private val viewModel by pixivValueViewModel({ MMKV.defaultMMKV() }, + responseStore = createResponseStore({ "home-square-${args.objectType}" })) { hint, prefStore -> + if (prefStore.getString(SessionManager.COOKIE_KEY, "").isNullOrEmpty()) { + throw CookieNotSyncException("Pixiv cookie not synced") + } + + Client.webApi.getSquareContents(args.objectType) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.GRID_AND_SECTION_HEADER) val adapter = CommonAdapter(viewLifecycleOwner) binding.listView.adapter = adapter binding.listView.updatePadding(left = 3.ppppx, right = 3.ppppx) - binding.listView.layoutManager = GridLayoutManager(context, 3).apply { - spanSizeLookup = object : SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if (binding.listView.adapter?.getItemViewType(position) == RedSectionHeaderHolder::class.java.hashCode()) { - 3 - } else { - 1 - } - } - } - } viewModel.result.observe(viewLifecycleOwner) { data -> val holders = mutableListOf() @@ -80,9 +73,10 @@ class SquareFragment : PixivFragment(R.layout.fragment_pixiv_list) { val webIllusts = mutableListOf() editorRecommend.forEach { recmd -> recmd.illustId?.let { id -> - data.body.thumbnails?.illust?.firstOrNull { it.id == id }?.let { webIllust -> - webIllusts.add(webIllust) - } + data.body.thumbnails?.illust?.firstOrNull { it.id == id } + ?.let { webIllust -> + webIllusts.add(webIllust) + } } } holders.add(RedSectionHeaderHolder("Editor Recommend Works")) @@ -133,8 +127,6 @@ class SquareFragment : PixivFragment(R.layout.fragment_pixiv_list) { } - - class RedSectionHeaderHolder( val title: String, val type: Int = 0, diff --git a/app/src/main/java/ceui/pixiv/ui/circles/CircleFragment.kt b/app/src/main/java/ceui/pixiv/ui/circles/CircleFragment.kt index 73b7699cf..e1bafa45a 100644 --- a/app/src/main/java/ceui/pixiv/ui/circles/CircleFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/circles/CircleFragment.kt @@ -29,6 +29,7 @@ import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.ResponseStore import ceui.pixiv.ui.common.TitledViewPagerFragment import ceui.pixiv.ui.common.constructVM +import ceui.pixiv.ui.common.createResponseStore import ceui.pixiv.ui.common.pixivValueViewModel import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.search.SearchIlllustMangaFragment @@ -50,16 +51,10 @@ class CircleFragment : TitledViewPagerFragment(R.layout.fragment_circle) { private val searchViewModel by constructVM({ args.keyword }) { word -> SearchViewModel(word) } - private val viewModel by pixivValueViewModel { - val responseStore = ResponseStore( - { "circle-detail-${args.keyword}" }, - expirationTimeMillis = 1800L, - CircleResponse::class.java, - dataLoader = { - Client.webApi.getCircleDetail(args.keyword) - } - ) - responseStore.retrieveData() + private val viewModel by pixivValueViewModel( + responseStore = createResponseStore({ "circle-detail-${args.keyword}" }) + ) { hint -> + Client.webApi.getCircleDetail(args.keyword) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -122,32 +117,38 @@ class CircleFragment : TitledViewPagerFragment(R.layout.fragment_circle) { windowInsets } - val adapter = SmartFragmentPagerAdapter(listOf( - PagedFragmentItem( - builder = { CircleInfoFragment() }, - initialTitle = getString(R.string.about_app) - ), - PagedFragmentItem( - builder = { - SearchIlllustMangaFragment() - }, - initialTitle = getString(R.string.string_136) - ), - PagedFragmentItem( - builder = { - SearchNovelFragment() - }, - initialTitle = getString(R.string.type_novel) - ), - PagedFragmentItem( - builder = { - SearchUserFragment() - }, - initialTitle = getString(R.string.type_user) - ) - ), this) + val adapter = SmartFragmentPagerAdapter( + listOf( + PagedFragmentItem( + builder = { CircleInfoFragment() }, + initialTitle = getString(R.string.about_app) + ), + PagedFragmentItem( + builder = { + SearchIlllustMangaFragment() + }, + initialTitle = getString(R.string.string_136) + ), + PagedFragmentItem( + builder = { + SearchNovelFragment() + }, + initialTitle = getString(R.string.type_novel) + ), + PagedFragmentItem( + builder = { + SearchUserFragment() + }, + initialTitle = getString(R.string.type_user) + ) + ), this + ) binding.circleViewPager.adapter = adapter - binding.tabLayoutList.setUpWith(binding.circleViewPager, binding.slidingCursor, viewLifecycleOwner) { + binding.tabLayoutList.setUpWith( + binding.circleViewPager, + binding.slidingCursor, + viewLifecycleOwner + ) { } } diff --git a/app/src/main/java/ceui/pixiv/ui/circles/CircleResultPreviewFragment.kt b/app/src/main/java/ceui/pixiv/ui/circles/CircleResultPreviewFragment.kt index 210eb15dc..1bb21ce5c 100644 --- a/app/src/main/java/ceui/pixiv/ui/circles/CircleResultPreviewFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/circles/CircleResultPreviewFragment.kt @@ -9,8 +9,10 @@ import ceui.lisa.view.SpacesItemDecoration import ceui.loxia.Client import ceui.pixiv.ui.common.CommonAdapter import ceui.pixiv.ui.common.IllustCardHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.pixivValueViewModel +import ceui.pixiv.ui.common.setUpLayoutManager import ceui.refactor.ppppx import ceui.refactor.viewBinding @@ -23,8 +25,7 @@ class CircleResultPreviewFragment : PixivFragment(R.layout.fragment_pixiv_list) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.listView.addItemDecoration(SpacesItemDecoration(4.ppppx)) - binding.listView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) + setUpLayoutManager(binding.listView, ListMode.STAGGERED_GRID) val adapter = CommonAdapter(viewLifecycleOwner) binding.listView.adapter = adapter viewModel.result.observe(viewLifecycleOwner) { resp -> diff --git a/app/src/main/java/ceui/pixiv/ui/comments/CommentsDataSource.kt b/app/src/main/java/ceui/pixiv/ui/comments/CommentsDataSource.kt index 228992b07..0c700465a 100644 --- a/app/src/main/java/ceui/pixiv/ui/comments/CommentsDataSource.kt +++ b/app/src/main/java/ceui/pixiv/ui/comments/CommentsDataSource.kt @@ -6,6 +6,7 @@ import ceui.loxia.Comment import ceui.loxia.CommentResponse import ceui.loxia.ObjectType import ceui.pixiv.ui.common.DataSource +import timber.log.Timber class CommentsDataSource( private val args: CommentsFragmentArgs, @@ -61,7 +62,7 @@ class CommentsDataSource( } itemHolders.value = updatedHolders } catch (ex: Exception) { - ex.printStackTrace() + Timber.e(ex) } } } diff --git a/app/src/main/java/ceui/pixiv/ui/comments/CommentsFragment.kt b/app/src/main/java/ceui/pixiv/ui/comments/CommentsFragment.kt index 5f71c755f..c30f8d658 100644 --- a/app/src/main/java/ceui/pixiv/ui/comments/CommentsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/comments/CommentsFragment.kt @@ -19,6 +19,7 @@ import ceui.loxia.Comment import ceui.loxia.ProgressTextButton import ceui.loxia.launchSuspend import ceui.pixiv.ui.common.BottomDividerDecoration +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -37,14 +38,7 @@ class CommentsFragment : PixivFragment(R.layout.fragment_pixiv_list), CommentAct override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.toolbarLayout.naviTitle.text = getString(R.string.comments) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - setUpRefreshState(binding, viewModel) - val dividerDecoration = BottomDividerDecoration( - requireContext(), - R.drawable.list_divider, - marginLeft = 48.ppppx - ) - binding.listView.addItemDecoration(dividerDecoration) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL_COMMENT) binding.bottomLayout.isVisible = true binding.bottomLayout.background = ColorDrawable(Color.parseColor("#66000000")) val childBinding = DataBindingUtil.inflate( diff --git a/app/src/main/java/ceui/pixiv/ui/common/CommonAdapter.kt b/app/src/main/java/ceui/pixiv/ui/common/CommonAdapter.kt index 7f967e5ed..f09f8497f 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/CommonAdapter.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/CommonAdapter.kt @@ -10,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import ceui.pixiv.ui.viewholdermap.ViewHolderFactory import ceui.refactor.setOnClick +import timber.log.Timber import java.lang.RuntimeException val listItemHolderDiffUtil = object : @@ -110,7 +111,7 @@ open class ListItemViewHolder(val bin try { it() } catch (ex: Exception) { - ex.printStackTrace() + Timber.e(ex) } } } diff --git a/app/src/main/java/ceui/pixiv/ui/common/DataSource.kt b/app/src/main/java/ceui/pixiv/ui/common/DataSource.kt index 3c9347910..3282a5437 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/DataSource.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/DataSource.kt @@ -11,9 +11,11 @@ import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext +import timber.log.Timber open class DataSource>( - private val dataFetcher: suspend () -> T, + private val dataFetcher: suspend (hint: RefreshHint) -> T, + private val responseStore: ResponseStore? = null, itemMapper: (Item) -> List, private val filter: (Item) -> Boolean = { _ -> true } ) { @@ -28,9 +30,6 @@ open class DataSource>( private val _itemHolders = MutableLiveData>() val itemHolders: LiveData> = _itemHolders - private val _liveNextUrl = MutableLiveData() - val liveNextUrl: LiveData = _liveNextUrl - private var _nextPageUrl: String? = null private val gson = Gson() @@ -45,23 +44,39 @@ open class DataSource>( if (hint == RefreshHint.ErrorRetry) { delay(300L) } - val response = withContext(Dispatchers.IO) { - dataFetcher() + + if (hint == RefreshHint.InitialLoad) { + responseStore?.loadFromCache()?.let { storedResponse -> + applyResponse(storedResponse, false) + } + } + + if (hint == RefreshHint.PullToRefresh || responseStore == null || responseStore.isCacheExpired()) { + val response = withContext(Dispatchers.IO) { + dataFetcher(hint).also { + responseStore?.writeToCache(it) + } + } + applyResponse(response, false) } - currentProtoItems.clear() - responseClass = response::class.java as Class - _nextPageUrl = response.nextPageUrl - _liveNextUrl.value = response.nextPageUrl - currentProtoItems.addAll(response.displayList) - mapProtoItemsToHolders() - _refreshState.value = RefreshState.LOADED( - hasContent = _itemHolders.value?.isNotEmpty() == true, - hasNext = _nextPageUrl?.isNotEmpty() == true - ) } catch (ex: Exception) { _refreshState.value = RefreshState.ERROR(ex) - ex.printStackTrace() + Timber.e(ex) + } + } + + private fun applyResponse(response: T, isLoadMore: Boolean) { + if (!isLoadMore) { + currentProtoItems.clear() } + responseClass = response::class.java as Class + _nextPageUrl = response.nextPageUrl + currentProtoItems.addAll(response.displayList) + mapProtoItemsToHolders() + _refreshState.value = RefreshState.LOADED( + hasContent = _itemHolders.value?.isNotEmpty() == true, + hasNext = _nextPageUrl?.isNotEmpty() == true + ) } open suspend fun loadMoreData() { @@ -73,20 +88,10 @@ open class DataSource>( val responseJson = responseBody.string() gson.fromJson(responseJson, responseClass) } - responseClass = response::class.java as Class - _nextPageUrl = response.nextPageUrl - _liveNextUrl.value = response.nextPageUrl - if (response.displayList.isNotEmpty()) { - currentProtoItems.addAll(response.displayList) - mapProtoItemsToHolders() - } - _refreshState.value = RefreshState.LOADED( - hasContent = _itemHolders.value?.isNotEmpty() == true, - hasNext = _nextPageUrl?.isNotEmpty() == true - ) + applyResponse(response, true) } catch (ex: Exception) { _refreshState.value = RefreshState.ERROR(ex) - ex.printStackTrace() + Timber.e(ex) } } @@ -107,17 +112,6 @@ open class DataSource>( } } - suspend fun loadOffsetData(pageIndex: Int) { - val nextPageUrl = _nextPageUrl ?: return - Common.showLog("dasasds aaa ${nextPageUrl}") - val newNextUrl = updateOffsetInUrl(nextPageUrl, pageIndex * 30) - _nextPageUrl = newNextUrl - _liveNextUrl.value = newNextUrl - currentProtoItems.clear() - Common.showLog("dasasds bbb ${newNextUrl}") - loadMoreData() - } - private fun mapProtoItemsToHolders() { val mapper = _variableItemMapper ?: return val holders = currentProtoItems @@ -128,9 +122,6 @@ open class DataSource>( _itemHolders.value = holders } - fun pickProtoItems(): List { - return currentProtoItems - } fun updateMapper(mapper: (Item) -> List) { _itemHolders.value = listOf() diff --git a/app/src/main/java/ceui/pixiv/ui/common/HoldersContainer.kt b/app/src/main/java/ceui/pixiv/ui/common/HoldersContainer.kt index a6ac8a10f..1dbd5df56 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/HoldersContainer.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/HoldersContainer.kt @@ -4,5 +4,4 @@ import androidx.lifecycle.LiveData interface HoldersContainer { val holders: LiveData> - val liveNextUrl: LiveData } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/common/HomeActivity.kt b/app/src/main/java/ceui/pixiv/ui/common/HomeActivity.kt index 98ed9f234..544534fef 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/HomeActivity.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/HomeActivity.kt @@ -21,11 +21,7 @@ class HomeActivity : AppCompatActivity(), INetworkState { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) _networkStateManager - try { - (this as? ComponentActivity)?.enableEdgeToEdge() - } catch (ex: Exception) { - ex.printStackTrace() - } + enableEdgeToEdge() binding = ActivityHomeBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/ceui/pixiv/ui/common/IllustCardHolder.kt b/app/src/main/java/ceui/pixiv/ui/common/IllustCardHolder.kt index a3c197b2e..85befa73d 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/IllustCardHolder.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/IllustCardHolder.kt @@ -8,6 +8,7 @@ import ceui.lisa.databinding.CellIllustCardBinding import ceui.lisa.utils.GlideUrlChild import ceui.loxia.Illust import ceui.loxia.ObjectPool +import ceui.loxia.ProgressIndicator import ceui.loxia.findActionReceiverOrNull import ceui.refactor.ppppx import ceui.refactor.screenWidth @@ -35,6 +36,8 @@ class IllustCardHolder(val illust: Illust) : ListItemHolder() { interface IllustCardActionReceiver { fun onClickIllustCard(illust: Illust) + + fun onClickBookmarkIllust(sender: ProgressIndicator, illustId: Long) } interface IllustIdActionReceiver { @@ -48,6 +51,8 @@ class IllustCardViewHolder(bd: CellIllustCardBinding) : override fun onBindViewHolder(holder: IllustCardHolder, position: Int) { super.onBindViewHolder(holder, position) + binding.illust = ObjectPool.get(holder.illust.id) + val itemWidth = ((screenWidth - 12.ppppx) / 2F).roundToInt() val itemHeight = (itemWidth * holder.illust.height / holder.illust.width.toFloat()).roundToInt() @@ -71,5 +76,9 @@ class IllustCardViewHolder(bd: CellIllustCardBinding) : it.findActionReceiverOrNull() ?.onClickIllustCard(holder.illust) } + binding.bookmark.setOnClick { + it.findActionReceiverOrNull() + ?.onClickBookmarkIllust(it, holder.illust.id) + } } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/common/ImgDisplayFragment.kt b/app/src/main/java/ceui/pixiv/ui/common/ImgDisplayFragment.kt index 88cdd45f9..2da6562da 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/ImgDisplayFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/ImgDisplayFragment.kt @@ -1,6 +1,5 @@ package ceui.pixiv.ui.common -import android.content.Context import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle @@ -17,14 +16,12 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import ceui.lisa.databinding.LayoutToolbarBinding import ceui.lisa.utils.Common import ceui.loxia.findActionReceiverOrNull import ceui.loxia.getHumanReadableMessage import ceui.loxia.observeEvent -import ceui.pixiv.ui.task.LoadTask import ceui.pixiv.ui.task.NamedUrl import ceui.pixiv.ui.task.TaskPool import ceui.pixiv.ui.task.TaskStatus @@ -41,6 +38,7 @@ import com.github.panpf.zoomimage.SketchZoomImageView import com.google.android.material.progressindicator.CircularProgressIndicator import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import timber.log.Timber import java.io.File import java.util.Locale @@ -67,8 +65,16 @@ abstract class ImgDisplayFragment(layoutId: Int) : PixivFragment(layoutId) { } } val activity = requireActivity() - val task = TaskPool.getLoadTask(NamedUrl(displayName(), contentUrl()), activity) - task.file.observe(viewLifecycleOwner) { file -> + val url = contentUrl() + if (url.isEmpty()) { + Timber.d("ImgDisplayFragment display img: empty") + return + } + + Timber.d("ImgDisplayFragment display img: ${url}") + val namedUrl = NamedUrl(displayName(), url) + val task = TaskPool.getLoadTask(namedUrl, activity) + task.result.observe(viewLifecycleOwner) { file -> displayImg.loadImage(file) downloadButton.setOnClick { val imageId = getImageIdInGallery(activity, displayName()) @@ -91,7 +97,7 @@ abstract class ImgDisplayFragment(layoutId: Int) : PixivFragment(layoutId) { } if (parentFragment is ViewPagerFragment) { viewPagerViewModel.downloadEvent.observeEvent(viewLifecycleOwner) { index -> - task.file.value?.let { file -> + task.result.value?.let { file -> saveImageToGallery(activity, file, displayName()) } } diff --git a/app/src/main/java/ceui/pixiv/ui/common/ListMode.kt b/app/src/main/java/ceui/pixiv/ui/common/ListMode.kt new file mode 100644 index 000000000..8415bc8c3 --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/common/ListMode.kt @@ -0,0 +1,11 @@ +package ceui.pixiv.ui.common + + +object ListMode { + const val VERTICAL = 1 + const val VERTICAL_NO_MARGIN = 2 + const val VERTICAL_COMMENT = 100 + const val STAGGERED_GRID = 3 + const val GRID = 4 + const val GRID_AND_SECTION_HEADER = 5 +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/common/MediaStoreUtils.kt b/app/src/main/java/ceui/pixiv/ui/common/MediaStoreUtils.kt index 5a791f4e6..4e7b89884 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/MediaStoreUtils.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/MediaStoreUtils.kt @@ -1,6 +1,7 @@ package ceui.pixiv.ui.common import android.content.ContentResolver +import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.database.Cursor @@ -13,14 +14,16 @@ import android.provider.MediaStore import ceui.lisa.R import ceui.lisa.utils.Common import com.blankj.utilcode.util.ImageUtils +import com.hjq.toast.ToastUtils +import timber.log.Timber import java.io.File import java.io.FileInputStream import java.io.IOException fun saveImageToGallery(context: Context, imageFile: File, displayName: String) { - try { - // Create content values for the image + runCatching { + // 创建ContentValues用于插入图片 val contentValues = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, displayName) // Specify the directory path in the Pictures folder @@ -30,84 +33,79 @@ fun saveImageToGallery(context: Context, imageFile: File, displayName: String) { ) } - val contentResolver = context.contentResolver ?: return + val contentResolver = context.contentResolver ?: return@runCatching // 检查contentResolver是否为null - // Insert the image into MediaStore and get the URI - val uri = contentResolver.insert( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - contentValues - ) ?: return + // 插入图片并获取URI + val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + ?: return@runCatching // 如果URI为空,返回 - // Open output stream to write the image data + // 将图片数据写入输出流 contentResolver.openOutputStream(uri)?.use { outputStream -> FileInputStream(imageFile).use { inputStream -> - // Copy the image file to the output stream - inputStream.copyTo(outputStream) + inputStream.copyTo(outputStream) // 复制文件内容到输出流 } - outputStream.flush() } - } catch (e: IOException) { - // Handle IO exceptions, e.g., file not found or I/O error - e.printStackTrace() - } catch (e: SecurityException) { - // Handle security exceptions, e.g., lack of permissions - e.printStackTrace() - } catch (e: Exception) { - // Handle any other unexpected exceptions - e.printStackTrace() + ToastUtils.show(context.getString(R.string.string_181)) + }.onFailure { ex -> + // 记录异常信息,方便调试 + when (ex) { + is IOException -> Timber.e("SaveImage IOException while saving image: ${ex.message}") + is SecurityException -> Timber.e("SaveImage SecurityException: Permission issue: ${ex.message}") + else -> Timber.e("SaveImage Unexpected error: ${ex.message}") + } } } -fun getImageIdInGallery(context: Context, displayName: String): Long? { - val contentResolver: ContentResolver = context.contentResolver - - // Define the projection to retrieve the URI of the image - val projection = arrayOf(MediaStore.Images.Media._ID) - // Define the selection criteria to match the displayName - val selection = "${MediaStore.Images.Media.DISPLAY_NAME} = ?" - val selectionArgs = arrayOf(displayName) +fun getImageIdInGallery(context: Context, displayName: String): Long? { + val contentResolver = context.contentResolver - // Query MediaStore for the image - val cursor = contentResolver.query( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - projection, - selection, - selectionArgs, - null - ) + // 查询 MediaStore 中匹配的图片 + return runCatching { + val projection = arrayOf(MediaStore.Images.Media._ID) + val selection = "${MediaStore.Images.Media.DISPLAY_NAME} = ?" + val selectionArgs = arrayOf(displayName) - // Extract the ID if available - val imageId = cursor?.use { - if (it.moveToFirst()) { - val idColumnIndex = it.getColumnIndex(MediaStore.Images.Media._ID) - it.getLong(idColumnIndex) - } else { + contentResolver.query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + projection, + selection, + selectionArgs, null + )?.use { cursor -> + if (cursor.moveToFirst()) { + val idColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID) + cursor.getLong(idColumnIndex) + } else { + null // 未找到匹配的图片 + } } - } - - return imageId + }.onFailure { ex -> + // 打印日志以便调试 + Timber.e(ex) + }.getOrNull() // 如果发生异常,返回 null } fun deleteImageById(context: Context, imageId: Long): Boolean { - val contentResolver: ContentResolver = context.contentResolver + // 获取 ContentResolver + val contentResolver = context.contentResolver - // Construct the Uri for the image using the imageId - val imageUri: Uri = Uri.withAppendedPath( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - imageId.toString() - ) + // 构造图片的 Uri + val imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageId) - return try { - // Delete the image from MediaStore + return runCatching { + // 从 MediaStore 删除图片 val rowsDeleted = contentResolver.delete(imageUri, null, null) - // Check if deletion was successful - rowsDeleted > 0 - } catch (e: Exception) { - // Handle any errors during deletion - e.printStackTrace() - false - } + if (rowsDeleted > 0) { + // 删除成功 + true + } else { + // 删除失败(可能未找到指定 ID 的图片) + false + } + }.onFailure { ex -> + // 打印异常日志,便于调试 + Timber.e(ex) + }.getOrDefault(false) // 如果发生异常,返回 false } diff --git a/app/src/main/java/ceui/pixiv/ui/common/PixivFragment.kt b/app/src/main/java/ceui/pixiv/ui/common/PixivFragment.kt index e99308c10..15a740fb3 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/PixivFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/PixivFragment.kt @@ -15,8 +15,11 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager import ceui.lisa.R import ceui.lisa.activities.UserActivity @@ -24,23 +27,23 @@ import ceui.lisa.databinding.FragmentPixivListBinding import ceui.lisa.databinding.LayoutToolbarBinding import ceui.lisa.utils.Common import ceui.lisa.utils.Params +import ceui.lisa.view.LinearItemDecoration import ceui.lisa.view.SpacesItemDecoration import ceui.loxia.Article -import ceui.loxia.FRAGMENT_RESULT_REQUEST_ID +import ceui.loxia.Client import ceui.loxia.Illust import ceui.loxia.Novel +import ceui.loxia.ObjectPool import ceui.loxia.ObjectType +import ceui.loxia.ProgressIndicator import ceui.loxia.RefreshHint import ceui.loxia.RefreshState import ceui.loxia.Tag import ceui.loxia.getHumanReadableMessage import ceui.loxia.launchSuspend -import ceui.loxia.listenToResultStore import ceui.loxia.observeEvent -import ceui.loxia.promptFragmentForResult import ceui.loxia.pushFragment -import ceui.pixiv.ui.bottom.ARG_ITEM_COUNT -import ceui.pixiv.ui.bottom.ItemListDialogFragment +import ceui.pixiv.ui.chats.RedSectionHeaderHolder import ceui.pixiv.ui.circles.CircleFragmentArgs import ceui.pixiv.ui.list.PixivListViewModel import ceui.pixiv.ui.novel.NovelTextFragmentArgs @@ -48,28 +51,24 @@ import ceui.pixiv.ui.user.UserActionReceiver import ceui.pixiv.ui.user.UserProfileFragmentArgs import ceui.pixiv.ui.web.WebFragmentArgs import ceui.pixiv.ui.works.IllustFragmentArgs -import ceui.pixiv.widgets.DialogViewModel -import ceui.pixiv.widgets.FragmentResultByFragment -import ceui.pixiv.widgets.FragmentResultStore -import ceui.pixiv.widgets.MenuItem import ceui.pixiv.widgets.TagsActionReceiver -import ceui.pixiv.widgets.showActionMenu import ceui.refactor.ppppx import ceui.refactor.setOnClick import com.scwang.smart.refresh.footer.ClassicsFooter import com.scwang.smart.refresh.header.FalsifyFooter import com.scwang.smart.refresh.header.MaterialHeader +import timber.log.Timber -interface FragmentResultRequestIdOwner { - val resultRequestId: String? - val fragmentUniqueId: String -} -open class PixivFragment(layoutId: Int) : Fragment(layoutId), FragmentResultRequestIdOwner, IllustCardActionReceiver, +open class PixivFragment(layoutId: Int) : Fragment(layoutId), IllustCardActionReceiver, UserActionReceiver, TagsActionReceiver, ArticleActionReceiver, NovelActionReceiver, IllustIdActionReceiver { private val fragmentViewModel: NavFragmentViewModel by viewModels() - private val fragmentResultStore by activityViewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Common.showLog("onCreate ${this::class.simpleName}") + } open fun onViewFirstCreated(view: View) { @@ -78,8 +77,6 @@ open class PixivFragment(layoutId: Int) : Fragment(layoutId), FragmentResultRequ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - listenToResultStore(fragmentResultStore) - if (fragmentViewModel.viewCreatedTime.value == null) { onViewFirstCreated(view) } @@ -88,17 +85,41 @@ open class PixivFragment(layoutId: Int) : Fragment(layoutId), FragmentResultRequ } override fun onClickIllustCard(illust: Illust) { - pushFragment( - R.id.navigation_illust, - IllustFragmentArgs(illust.id).toBundle() - ) + onClickIllust(illust.id) + } + + override fun onClickBookmarkIllust(sender: ProgressIndicator, illustId: Long) { + launchSuspend(sender) { + val illust = ObjectPool.get(illustId).value ?: Client.appApi.getIllust(illustId).illust?.also { ObjectPool.update(it) } + if (illust != null) { + if (illust.is_bookmarked == true) { + Client.appApi.removeBookmark(illustId) + ObjectPool.update( + illust.copy( + is_bookmarked = false, + total_bookmarks = illust.total_bookmarks?.minus(1) + ) + ) + Common.showToast(getString(R.string.cancel_like_illust)) + } else { + Client.appApi.postBookmark(illustId) + ObjectPool.update( + illust.copy( + is_bookmarked = true, + total_bookmarks = illust.total_bookmarks?.plus(1) + ) + ) + Common.showToast(getString(R.string.like_novel_success_public)) + } + } + } } override fun onClickUser(id: Long) { try { pushFragment(R.id.navigation_user_profile, UserProfileFragmentArgs(id).toBundle()) } catch (ex: Exception) { - ex.printStackTrace() + Timber.e(ex) val userIntent = Intent( requireContext(), UserActivity::class.java @@ -140,27 +161,10 @@ open class PixivFragment(layoutId: Int) : Fragment(layoutId), FragmentResultRequ ) } - fun setFragmentResult(result: T) { - resultRequestId?.let { requestId -> - val existingLifecycle = fragmentResultStore.getExistingLifecycle(requestId) - if (existingLifecycle != null && existingLifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { - fragmentResultStore.getTypedResult(requestId)?.let { result -> - Common.showLog("dsaasdw STARTED getTypedResult ${result}") -// fragmentResultStore.getTypedTask(requestId)?.let { task -> -// task.complete(FragmentResultByFragment(result, fragment)) -// fragmentResultStore.removeResult(requestId) -// } - } - } else { - fragmentResultStore.putResult(requestId, result) - } - } + override fun onDestroy() { + super.onDestroy() + Common.showLog("onDestroy ${this::class.simpleName}") } - - override val resultRequestId: String? - get() = arguments?.getString(FRAGMENT_RESULT_REQUEST_ID) - override val fragmentUniqueId: String - get() = fragmentViewModel.fragmentUniqueId } interface ViewPagerFragment { @@ -198,7 +202,9 @@ fun Fragment.setUpToolbar(binding: LayoutToolbarBinding, content: ViewGroup) { binding.toolbarLayout.background = ColorDrawable( Common.resolveThemeAttribute(requireContext(), androidx.appcompat.R.attr.colorPrimary) ) - requireActivity().finish() + binding.naviBack.setOnClick { + requireActivity().finish() + } } ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) @@ -209,8 +215,9 @@ fun Fragment.setUpToolbar(binding: LayoutToolbarBinding, content: ViewGroup) { } } -fun Fragment.setUpRefreshState(binding: FragmentPixivListBinding, viewModel: RefreshOwner) { +fun Fragment.setUpRefreshState(binding: FragmentPixivListBinding, viewModel: RefreshOwner, listMode: Int = ListMode.STAGGERED_GRID) { setUpToolbar(binding.toolbarLayout, binding.listView) + setUpLayoutManager(binding.listView, listMode) val ctx = requireContext() binding.refreshLayout.setRefreshHeader(MaterialHeader(ctx)) binding.refreshLayout.setOnRefreshListener { @@ -263,61 +270,40 @@ fun Fragment.setUpRefreshState(binding: FragmentPixivListBinding, viewModel: Ref viewModel.holders.observe(viewLifecycleOwner) { holders -> adapter.submitList(holders) } -// viewModel.liveNextUrl.observe(viewLifecycleOwner) { url -> -// binding.nextUrl.text = url -// } } } - -private fun calculateTotalPages(totalItems: Int, pageSize: Int): Int { - return if (totalItems % pageSize == 0) { - totalItems / pageSize - } else { - (totalItems / pageSize) + 1 - } -} - -fun FragmentT.setUpSizedList(binding: FragmentPixivListBinding, dataSource: DataSourceContainer<*, *>, listFullSize: Int) where FragmentT : Fragment, FragmentT : FragmentResultRequestIdOwner { - val self = this - val onResult: FragmentT.(Int) -> Unit = { result -> - Common.showLog("dsaasdw ${fragmentUniqueId} really get ${result} ${lifecycle.currentState}") - } - val dialogViewModel by activityViewModels() - if (listFullSize > 30) { - binding.listSetting.isVisible = true - dialogViewModel.triggerOffsetPageEvent.observeEvent(this) { page -> - launchSuspend { - dataSource.dataSource().loadOffsetData(page) +fun Fragment.setUpLayoutManager(listView: RecyclerView, listMode: Int = ListMode.STAGGERED_GRID) { + val ctx = requireContext() + listView.itemAnimator = null + if (listMode == ListMode.STAGGERED_GRID) { + listView.addItemDecoration(SpacesItemDecoration(4.ppppx)) + listView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) + } else if (listMode == ListMode.VERTICAL) { + listView.layoutManager = LinearLayoutManager(ctx) + listView.addItemDecoration(LinearItemDecoration(18.ppppx)) + } else if (listMode == ListMode.VERTICAL_COMMENT) { + listView.layoutManager = LinearLayoutManager(requireContext()) + listView.addItemDecoration(BottomDividerDecoration( + requireContext(), + R.drawable.list_divider, + marginLeft = 48.ppppx + )) + } else if (listMode == ListMode.VERTICAL_NO_MARGIN) { + listView.layoutManager = LinearLayoutManager(ctx) + } else if (listMode == ListMode.GRID) { + listView.layoutManager = GridLayoutManager(ctx, 3) + } else if (listMode == ListMode.GRID_AND_SECTION_HEADER) { + listView.layoutManager = GridLayoutManager(ctx, 3).apply { + spanSizeLookup = object : SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (listView.adapter?.getItemViewType(position) == RedSectionHeaderHolder::class.java.hashCode()) { + 3 + } else { + 1 + } + } } } - binding.listSetting.setOnClick { -// showActionMenu { -// add( -// MenuItem("按照页数查看作品", "实验性功能,测试中") { -// -// } -// ) -// } - -// self.promptFragmentForResult( -// dialogProducer = { -// ItemListDialogFragment() -// }, -// bundle = Bundle().apply { -// val itemCount = calculateTotalPages(listFullSize, 30) -// putInt(ARG_ITEM_COUNT, itemCount) -// }, -// onResult -// ) - } - } else { - binding.listSetting.isVisible = false } } - -fun Fragment.setUpStaggerLayout(binding: FragmentPixivListBinding, viewModel: PixivListViewModel<*, *>) { - setUpRefreshState(binding, viewModel) - binding.listView.addItemDecoration(SpacesItemDecoration(4.ppppx)) - binding.listView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) -} diff --git a/app/src/main/java/ceui/pixiv/ui/common/ResponseStore.kt b/app/src/main/java/ceui/pixiv/ui/common/ResponseStore.kt index f6a400633..fb8f194a6 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/ResponseStore.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/ResponseStore.kt @@ -1,14 +1,17 @@ package ceui.pixiv.ui.common +import ceui.loxia.Client +import ceui.loxia.RefreshHint import com.google.gson.Gson import com.tencent.mmkv.MMKV import kotlinx.coroutines.delay +import timber.log.Timber +import java.lang.reflect.Method -class ResponseStore( +class ResponseStore private constructor( private val keyProvider: () -> String, private val expirationTimeMillis: Long, - private val typeToken: Class, - private val dataLoader: suspend () -> T + private val typeToken: Class ) { private val gson = Gson() @@ -23,35 +26,50 @@ class ResponseStore( MMKV.mmkvWithID("api-cache") } - suspend fun retrieveData(): T { - val cacheTimestamp = preferences.getLong(timeKey, 0L) + fun writeToCache(data: T) { val currentTime = System.currentTimeMillis() - - return if (isCacheExpired(cacheTimestamp, currentTime)) { - fetchAndCacheData(currentTime) - } else { - loadFromCache(currentTime) - } + preferences.putString(jsonKey, gson.toJson(data)) + preferences.putLong(timeKey, currentTime) } - private fun isCacheExpired(cacheTimestamp: Long, currentTime: Long): Boolean { + fun isCacheExpired(): Boolean { + val currentTime = System.currentTimeMillis() + val cacheTimestamp = preferences.getLong(timeKey, 0L) return (currentTime - cacheTimestamp) > expirationTimeMillis } - private suspend fun fetchAndCacheData(currentTime: Long): T { - val data = dataLoader() - preferences.putString(jsonKey, gson.toJson(data)) - preferences.putLong(timeKey, currentTime) - return data + fun loadFromCache(): T? { + return try { + val json = preferences.getString(jsonKey, null) + if (json?.isNotEmpty() == true) { + gson.fromJson(json, typeToken) + } else { + null + } + } catch (ex: Exception) { + Timber.e(ex) + null + } } - private suspend fun loadFromCache(currentTime: Long): T { - val json = preferences.getString(jsonKey, null) - return try { - delay(200L) - gson.fromJson(json, typeToken) - } catch (e: Exception) { - fetchAndCacheData(currentTime) + companion object { + fun create( + keyProvider: () -> String, + expirationTimeMillis: Long, + typeToken: Class + ): ResponseStore { + return ResponseStore(keyProvider, expirationTimeMillis, typeToken) } } } + +inline fun createResponseStore( + noinline keyProvider: () -> String, + expirationTimeMillis: Long = 30 * 60 * 1000L, +): ResponseStore { + return ResponseStore.create( + keyProvider = keyProvider, + expirationTimeMillis = expirationTimeMillis, + typeToken = T::class.java, + ) +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/common/ValueViewModel.kt b/app/src/main/java/ceui/pixiv/ui/common/ValueViewModel.kt index 4c0c2e1c4..809dcf0a2 100644 --- a/app/src/main/java/ceui/pixiv/ui/common/ValueViewModel.kt +++ b/app/src/main/java/ceui/pixiv/ui/common/ValueViewModel.kt @@ -10,16 +10,20 @@ import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.viewModelScope import ceui.loxia.RefreshHint import ceui.loxia.RefreshState -import ceui.loxia.slinkyViewModels +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber fun Fragment.pixivValueViewModel( - loader: suspend () -> T, + dataFetcher: suspend (hint: RefreshHint) -> T, + responseStore: ResponseStore? = null, ): Lazy> { return this.viewModels { object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return ValueViewModel(loader) as T + return ValueViewModel(dataFetcher, responseStore) as T } } } @@ -27,15 +31,16 @@ fun Fragment.pixivValueViewModel( inline fun Fragment.pixivValueViewModel( noinline argsProducer: () -> ArgsT, - noinline loader: suspend (ArgsT) -> T, + responseStore: ResponseStore? = null, + noinline dataFetcher: suspend (hint: RefreshHint, ArgsT) -> T, ): Lazy> { return this.viewModels { object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { val args = argsProducer() - return ValueViewModel(loader = { - loader(args) - }) as T + return ValueViewModel(dataFetcher = { hint -> + dataFetcher(hint, args) + }, responseStore) as T } } } @@ -43,12 +48,13 @@ inline fun Fragment.pixivValueViewModel( inline fun Fragment.pixivValueViewModel( noinline ownerProducer: () -> ViewModelStoreOwner = { this }, - noinline loader: suspend () -> T, + responseStore: ResponseStore? = null, + noinline dataFetcher: suspend (hint: RefreshHint) -> T, ): Lazy> { return this.viewModels(ownerProducer = ownerProducer) { object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return ValueViewModel(loader) as T + return ValueViewModel(dataFetcher, responseStore) as T } } } @@ -56,7 +62,8 @@ inline fun Fragment.pixivValueViewModel( class ValueViewModel( - private val loader: suspend () -> T, + private val dataFetcher: suspend (hint: RefreshHint) -> T, + private val responseStore: ResponseStore? = null, ) : ViewModel(), RefreshOwner { private val _refreshState = MutableLiveData() @@ -73,15 +80,32 @@ class ValueViewModel( viewModelScope.launch { try { _refreshState.value = RefreshState.LOADING(refreshHint = hint) - val response = loader() - _result.value = response + if (hint == RefreshHint.ErrorRetry) { + delay(300L) + } + + if (hint == RefreshHint.InitialLoad) { + responseStore?.loadFromCache()?.let { storedResponse -> + _result.value = storedResponse + } + } + + if (hint == RefreshHint.PullToRefresh || responseStore == null || responseStore.isCacheExpired()) { + val response = withContext(Dispatchers.IO) { + dataFetcher(hint).also { + responseStore?.writeToCache(it) + } + } + _result.value = response + } + _refreshState.value = RefreshState.LOADED( hasContent = true, hasNext = false ) } catch (ex: Exception) { _refreshState.value = RefreshState.ERROR(ex) - ex.printStackTrace() + Timber.e(ex) } } } diff --git a/app/src/main/java/ceui/pixiv/ui/discover/DiscoverFragment.kt b/app/src/main/java/ceui/pixiv/ui/discover/DiscoverFragment.kt index 2918e6140..525077863 100644 --- a/app/src/main/java/ceui/pixiv/ui/discover/DiscoverFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/discover/DiscoverFragment.kt @@ -2,23 +2,37 @@ package ceui.pixiv.ui.discover import android.os.Bundle import android.view.View +import android.widget.LinearLayout import androidx.fragment.app.viewModels import androidx.lifecycle.MutableLiveData +import androidx.recyclerview.widget.LinearLayoutManager import ceui.lisa.R import ceui.lisa.databinding.FragmentDiscoverBinding +import ceui.loxia.Client import ceui.loxia.ObjectType +import ceui.loxia.pushFragment +import ceui.pixiv.ui.chats.IllustSquareHolder import ceui.pixiv.ui.circles.CircleFragment import ceui.pixiv.ui.circles.PagedFragmentItem import ceui.pixiv.ui.circles.SmartFragmentPagerAdapter +import ceui.pixiv.ui.common.CommonAdapter import ceui.pixiv.ui.common.CommonViewPagerViewModel import ceui.pixiv.ui.common.HomeTabContainer import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.TitledViewPagerFragment +import ceui.pixiv.ui.common.createResponseStore +import ceui.pixiv.ui.common.pixivValueViewModel import ceui.pixiv.ui.home.RecmdIllustMangaFragment import ceui.pixiv.ui.home.RecmdIllustMangaFragmentArgs import ceui.pixiv.ui.home.RecmdNovelFragment +import ceui.pixiv.ui.rank.RankPreviewHolder +import ceui.pixiv.ui.rank.RankingIllustsFragment +import ceui.pixiv.ui.rank.RankingIllustsFragmentArgs import ceui.pixiv.widgets.setUpWith +import ceui.refactor.setOnClick import ceui.refactor.viewBinding +import com.tencent.mmkv.MMKV +import timber.log.Timber class DiscoverFragment : TitledViewPagerFragment(R.layout.fragment_discover), HomeTabContainer { diff --git a/app/src/main/java/ceui/pixiv/ui/home/HomeViewPagerFragment.kt b/app/src/main/java/ceui/pixiv/ui/home/HomeViewPagerFragment.kt index 51c54dc6d..864fea362 100644 --- a/app/src/main/java/ceui/pixiv/ui/home/HomeViewPagerFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/home/HomeViewPagerFragment.kt @@ -23,7 +23,6 @@ import ceui.lisa.databinding.FragmentHomeViewpagerBinding import ceui.lisa.utils.Common import ceui.loxia.Illust import ceui.loxia.pushFragment -import ceui.loxia.pushFragmentForResult import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.ViewPagerFragment import ceui.pixiv.session.SessionManager @@ -39,7 +38,6 @@ import ceui.pixiv.ui.rank.RankingIllustsFragmentArgs import ceui.pixiv.ui.task.LoadTask import ceui.pixiv.ui.task.NamedUrl import ceui.pixiv.ui.user.following.FollowingViewPagerFragment -import ceui.pixiv.widgets.FragmentResultByFragment import ceui.pixiv.widgets.alertYesOrCancel import ceui.refactor.setOnClick import ceui.refactor.viewBinding @@ -79,19 +77,6 @@ class HomeViewPagerFragment : PixivFragment(R.layout.fragment_home_viewpager), V } binding.naviSearch.setOnClick { pushFragment(R.id.navigation_search_viewpager) -// SessionManager.testRenewAnim() -// val task = LoadTask(NamedUrl("test", "https://i.pximg.net/c/240x480_80/novel-cover-master/img/2023/02/19/22/32/21/ci19338210_456a179fb6c90b910911e9075874eda8_master1200.jpg"), requireActivity(), true) -// task.file.observe(viewLifecycleOwner) { file -> -// val resolution = getImageDimensions(file) -// Common.showLog("sadasd2 bb ${resolution}") -// Common.showLog("sadasd2 cc ${getFileSize(file)}") -// } -// -// pushFragmentForResult( -// R.id.navigation_ranking_illust_fragment, -// RankingIllustsFragmentArgs("day").toBundle(), -// HomeViewPagerFragment::onFragmentResult -// ) } binding.account = SessionManager.loggedInAccount @@ -151,8 +136,4 @@ class HomeViewPagerFragment : PixivFragment(R.layout.fragment_home_viewpager), V } } } - - private fun onFragmentResult(result: HelloResult) { - Common.showLog("dsaasdw ${fragmentUniqueId} really get ${result} ${lifecycle.currentState}") - } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaDataSource.kt b/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaDataSource.kt index ddb037fb4..d24093243 100644 --- a/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaDataSource.kt +++ b/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaDataSource.kt @@ -6,16 +6,13 @@ import ceui.loxia.Illust import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.ResponseStore +import ceui.pixiv.ui.common.createResponseStore +import timber.log.Timber class RecmdIllustMangaDataSource( private val args: RecmdIllustMangaFragmentArgs, - private val responseStore: ResponseStore = ResponseStore( - { "home-recommend-${args.objectType}-api" }, - 1800 * 1000L, - HomeIllustResponse::class.java, - { Client.appApi.getHomeData(args.objectType) } - ) ) : DataSource( - dataFetcher = { responseStore.retrieveData() }, + dataFetcher = { hint -> Client.appApi.getHomeData(args.objectType) }, + responseStore = createResponseStore({ "home-recommend-${args.objectType}-api" }), itemMapper = { illust -> listOf(IllustCardHolder(illust)) }, ) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaFragment.kt b/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaFragment.kt index fe0c5544e..9cae51cb9 100644 --- a/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/home/RecmdIllustMangaFragment.kt @@ -6,18 +6,20 @@ import androidx.navigation.fragment.navArgs import ceui.lisa.R import ceui.lisa.databinding.FragmentPixivListBinding import ceui.pixiv.ui.common.PixivFragment -import ceui.pixiv.ui.common.setUpStaggerLayout +import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel import ceui.refactor.viewBinding class RecmdIllustMangaFragment : PixivFragment(R.layout.fragment_pixiv_list) { private val binding by viewBinding(FragmentPixivListBinding::bind) - private val args by navArgs() - private val viewModel by pixivListViewModel { RecmdIllustMangaDataSource(args) } + private val safeArgs by navArgs() + private val viewModel by pixivListViewModel({ safeArgs }) { args -> + RecmdIllustMangaDataSource(args) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpStaggerLayout(binding, viewModel) + setUpRefreshState(binding, viewModel) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelDataSource.kt b/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelDataSource.kt index 924461354..46f2be819 100644 --- a/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelDataSource.kt +++ b/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelDataSource.kt @@ -9,18 +9,14 @@ import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.NovelCardHolder import ceui.pixiv.ui.common.ResponseStore +import ceui.pixiv.ui.common.createResponseStore class RecmdNovelDataSource( - private val responseStore: ResponseStore = ResponseStore( - { "home-recommend-novel-api" }, - 1800 * 1000L, - NovelResponse::class.java, - { Client.appApi.getRecmdNovels() } - ) ) : DataSource( - dataFetcher = { - responseStore.retrieveData() + dataFetcher = { hint -> + Client.appApi.getRecmdNovels() }, + responseStore = createResponseStore({ "home-recommend-novel-api" }), itemMapper = { novel -> listOf(NovelCardHolder(novel)) }, filter = { novel -> novel.visible != false } ) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelFragment.kt b/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelFragment.kt index 6adef6f62..ae88574a9 100644 --- a/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/home/RecmdNovelFragment.kt @@ -10,6 +10,7 @@ import ceui.lisa.view.LinearItemDecoration import ceui.lisa.view.NovelItemDecoration import ceui.loxia.Client import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.NovelCardHolder import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.PvisionCardHolder @@ -25,8 +26,6 @@ class RecmdNovelFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - binding.listView.addItemDecoration(NovelItemDecoration(10.ppppx)) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/home/WalkthroughFragment.kt b/app/src/main/java/ceui/pixiv/ui/home/WalkthroughFragment.kt index 2cf5867b2..10ea5c3cd 100644 --- a/app/src/main/java/ceui/pixiv/ui/home/WalkthroughFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/home/WalkthroughFragment.kt @@ -13,7 +13,8 @@ import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.ResponseStore -import ceui.pixiv.ui.common.setUpStaggerLayout +import ceui.pixiv.ui.common.createResponseStore +import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel import ceui.refactor.viewBinding @@ -24,18 +25,12 @@ class WalkthroughFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpStaggerLayout(binding, viewModel) + setUpRefreshState(binding, viewModel) } } -class WalkthroughDataSource( - private val responseStore: ResponseStore = ResponseStore( - { "home-walkthrough-api" }, - 1800 * 1000L, - IllustResponse::class.java, - { Client.appApi.getWalkthroughWorks() } - ) -) : DataSource( - dataFetcher = { responseStore.retrieveData() }, +class WalkthroughDataSource : DataSource( + dataFetcher = { hint -> Client.appApi.getWalkthroughWorks() }, + responseStore = createResponseStore({ "home-walkthrough-api" }), itemMapper = { illust -> listOf(IllustCardHolder(illust)) }, ) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/list/PixivListViewModel.kt b/app/src/main/java/ceui/pixiv/ui/list/PixivListViewModel.kt index e0a74f107..71a52098b 100644 --- a/app/src/main/java/ceui/pixiv/ui/list/PixivListViewModel.kt +++ b/app/src/main/java/ceui/pixiv/ui/list/PixivListViewModel.kt @@ -52,7 +52,6 @@ class PixivListViewModel>( override val refreshState: LiveData = _dataSource.refreshState override val holders: LiveData> = _dataSource.itemHolders - override val liveNextUrl: LiveData = _dataSource.liveNextUrl init { if (_dataSource.initialLoad()) { diff --git a/app/src/main/java/ceui/pixiv/ui/novel/NovelTextFragment.kt b/app/src/main/java/ceui/pixiv/ui/novel/NovelTextFragment.kt index 765b1d81c..da180c2a5 100644 --- a/app/src/main/java/ceui/pixiv/ui/novel/NovelTextFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/novel/NovelTextFragment.kt @@ -20,6 +20,7 @@ import ceui.loxia.WebNovel import ceui.loxia.pushFragment import ceui.pixiv.ui.comments.CommentsFragmentArgs import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -56,8 +57,7 @@ class NovelTextFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) val authorId = ObjectPool.get(args.novelId).value?.user?.id ?: 0L binding.toolbarLayout.naviMore.setOnClick { pushFragment(R.id.navigation_comments_illust, CommentsFragmentArgs(args.novelId, authorId, ObjectType.NOVEL).toBundle()) diff --git a/app/src/main/java/ceui/pixiv/ui/rank/RankFragment.kt b/app/src/main/java/ceui/pixiv/ui/rank/RankFragment.kt new file mode 100644 index 000000000..c23af5027 --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/rank/RankFragment.kt @@ -0,0 +1,59 @@ +package ceui.pixiv.ui.rank + +import android.os.Bundle +import android.view.View +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import ceui.lisa.R +import ceui.lisa.databinding.FragmentRankViewpagerBinding +import ceui.pixiv.ui.circles.PagedFragmentItem +import ceui.pixiv.ui.circles.SmartFragmentPagerAdapter +import ceui.pixiv.ui.common.TitledViewPagerFragment +import ceui.pixiv.widgets.setUpWith +import ceui.refactor.viewBinding + +class RankFragment : TitledViewPagerFragment(R.layout.fragment_rank_viewpager) { + + private val binding by viewBinding(FragmentRankViewpagerBinding::bind) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + binding.rootLayout.updatePadding(0, insets.top, 0, 0) + windowInsets + } + val adapter = SmartFragmentPagerAdapter( + listOf( + PagedFragmentItem( + builder = { + RankingIllustsFragment().apply { + arguments = RankingIllustsFragmentArgs("day").toBundle() + } + }, + initialTitle = getString(R.string.daily_rank) + ), + PagedFragmentItem( + builder = { + RankingIllustsFragment().apply { + arguments = RankingIllustsFragmentArgs("week").toBundle() + } + }, + initialTitle = getString(R.string.weekly_rank) + ), + PagedFragmentItem( + builder = { + RankingIllustsFragment().apply { + arguments = RankingIllustsFragmentArgs("month").toBundle() + } + }, + initialTitle = getString(R.string.monthly_rank) + ), + ), + this + ) + binding.rankViewpager.adapter = adapter + binding.tabLayoutList.setUpWith(binding.rankViewpager, binding.slidingCursor, viewLifecycleOwner, {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/rank/RankPreviewFragment.kt b/app/src/main/java/ceui/pixiv/ui/rank/RankPreviewFragment.kt new file mode 100644 index 000000000..ca0a1daee --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/rank/RankPreviewFragment.kt @@ -0,0 +1,40 @@ +package ceui.pixiv.ui.rank + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import ceui.pixiv.ui.common.PixivFragment +import ceui.lisa.R +import ceui.lisa.databinding.FragmentRankPreviewBinding +import ceui.loxia.Client +import ceui.loxia.pushFragment +import ceui.pixiv.ui.common.CommonAdapter +import ceui.pixiv.ui.common.createResponseStore +import ceui.pixiv.ui.common.pixivValueViewModel +import ceui.refactor.setOnClick +import ceui.refactor.viewBinding +import kotlin.getValue + +class RankPreviewFragment : PixivFragment(R.layout.fragment_rank_preview) { + + private val binding by viewBinding(FragmentRankPreviewBinding::bind) + private val rankIllustViewModel by pixivValueViewModel( + responseStore = createResponseStore({ "rank-illust-day" }) + ) { hint -> + Client.appApi.getRankingIllusts("day") + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.rankItems.setOnClick { + pushFragment(R.id.navigation_rank) + } + val rankingAdapter = CommonAdapter(viewLifecycleOwner) + binding.rankPreviewList.adapter = rankingAdapter + binding.rankPreviewList.layoutManager = LinearLayoutManager(requireContext(), + LinearLayoutManager.HORIZONTAL, false) + rankIllustViewModel.result.observe(viewLifecycleOwner) { resp -> + rankingAdapter.submitList(resp.displayList.map { RankPreviewHolder(it) }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/rank/RankPreviewHolder.kt b/app/src/main/java/ceui/pixiv/ui/rank/RankPreviewHolder.kt new file mode 100644 index 000000000..c40098e8c --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/rank/RankPreviewHolder.kt @@ -0,0 +1,46 @@ +package ceui.pixiv.ui.rank + +import androidx.core.view.updateLayoutParams +import ceui.lisa.annotations.ItemHolder +import ceui.lisa.databinding.ItemIllustSquareBinding +import ceui.lisa.utils.GlideUrlChild +import ceui.loxia.Illust +import ceui.loxia.WebIllust +import ceui.loxia.findActionReceiverOrNull +import ceui.pixiv.ui.common.IllustCardActionReceiver +import ceui.pixiv.ui.common.ListItemHolder +import ceui.pixiv.ui.common.ListItemViewHolder +import ceui.refactor.ppppx +import ceui.refactor.setOnClick +import com.bumptech.glide.Glide + + + +class RankPreviewHolder(val illust: Illust) : ListItemHolder() { + + override fun getItemId(): Long { + return illust.id + } +} + + +@ItemHolder(RankPreviewHolder::class) +class RankPreviewViewHolder(aa: ItemIllustSquareBinding) : + ListItemViewHolder(aa) { + + override fun onBindViewHolder(holder: RankPreviewHolder, position: Int) { + super.onBindViewHolder(holder, position) + Glide.with(context) + .load(GlideUrlChild(holder.illust.image_urls?.square_medium)) + .into(binding.squareImage) + val w = 170.ppppx + binding.root.updateLayoutParams { + width = w + height = w + } + binding.squareImage.setOnClick { + it.findActionReceiverOrNull() + ?.onClickIllustCard(holder.illust) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/rank/RankingIllustsFragment.kt b/app/src/main/java/ceui/pixiv/ui/rank/RankingIllustsFragment.kt index ad57f423b..f6efd5fc7 100644 --- a/app/src/main/java/ceui/pixiv/ui/rank/RankingIllustsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/rank/RankingIllustsFragment.kt @@ -6,27 +6,30 @@ import androidx.navigation.fragment.findNavController import ceui.lisa.R import ceui.lisa.databinding.FragmentPixivListBinding import ceui.loxia.Client +import ceui.loxia.IllustResponse +import ceui.loxia.threadSafeArgs import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.list.pixivListViewModel -import ceui.pixiv.ui.common.setUpStaggerLayout import ceui.pixiv.ui.common.IllustCardHolder -import ceui.pixiv.ui.home.HelloResult -import ceui.refactor.setOnClick +import ceui.pixiv.ui.common.createResponseStore +import ceui.pixiv.ui.common.setUpRefreshState import ceui.refactor.viewBinding class RankingIllustsFragment : PixivFragment(R.layout.fragment_pixiv_list) { private val binding by viewBinding(FragmentPixivListBinding::bind) - private val viewModel by pixivListViewModel { + private val safeArgs by threadSafeArgs() + private val viewModel by pixivListViewModel({ safeArgs.mode }) { mode -> DataSource( - dataFetcher = { Client.appApi.getRankingIllusts("day") }, + dataFetcher = { hint -> Client.appApi.getRankingIllusts(mode) }, + responseStore = createResponseStore({ "rank-illust-$mode" }), itemMapper = { illust -> listOf(IllustCardHolder(illust)) } ) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpStaggerLayout(binding, viewModel) + setUpRefreshState(binding, viewModel) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/related/RelatedIllustsFragment.kt b/app/src/main/java/ceui/pixiv/ui/related/RelatedIllustsFragment.kt new file mode 100644 index 000000000..1f1142aaa --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/related/RelatedIllustsFragment.kt @@ -0,0 +1,36 @@ +package ceui.pixiv.ui.related + +import android.os.Bundle +import android.view.View +import androidx.navigation.fragment.findNavController +import ceui.lisa.R +import ceui.lisa.databinding.FragmentPixivListBinding +import ceui.loxia.Client +import ceui.loxia.IllustResponse +import ceui.loxia.threadSafeArgs +import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.PixivFragment +import ceui.pixiv.ui.list.pixivListViewModel +import ceui.pixiv.ui.common.IllustCardHolder +import ceui.pixiv.ui.common.createResponseStore +import ceui.pixiv.ui.common.setUpRefreshState +import ceui.pixiv.ui.rank.RankingIllustsFragmentArgs +import ceui.refactor.viewBinding + +class RelatedIllustsFragment : PixivFragment(R.layout.fragment_pixiv_list) { + + private val binding by viewBinding(FragmentPixivListBinding::bind) + private val safeArgs by threadSafeArgs() + private val viewModel by pixivListViewModel({ safeArgs.illustId }) { illustId -> + DataSource( + dataFetcher = { hint -> Client.appApi.getRelatedIllusts(illustId) }, + responseStore = createResponseStore({ "related-illust-$illustId" }), + itemMapper = { illust -> listOf(IllustCardHolder(illust)) } + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpRefreshState(binding, viewModel) + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/search/SearchIlllustMangaFragment.kt b/app/src/main/java/ceui/pixiv/ui/search/SearchIlllustMangaFragment.kt index 6b2bc03a5..93f263ae6 100644 --- a/app/src/main/java/ceui/pixiv/ui/search/SearchIlllustMangaFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/search/SearchIlllustMangaFragment.kt @@ -12,7 +12,7 @@ import ceui.loxia.RefreshHint import ceui.loxia.observeEvent import ceui.pixiv.ui.bottom.UsersYoriDialogFragment import ceui.pixiv.ui.common.PixivFragment -import ceui.pixiv.ui.common.setUpStaggerLayout +import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel import ceui.pixiv.widgets.DialogViewModel import ceui.refactor.setOnClick @@ -33,7 +33,7 @@ class SearchIlllustMangaFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpStaggerLayout(binding, viewModel) + setUpRefreshState(binding, viewModel) binding.radioTab.setTabs(listOf( "热度预览", "从新到旧", diff --git a/app/src/main/java/ceui/pixiv/ui/search/SearchNovelFragment.kt b/app/src/main/java/ceui/pixiv/ui/search/SearchNovelFragment.kt index 7c2726ea9..b4492b36a 100644 --- a/app/src/main/java/ceui/pixiv/ui/search/SearchNovelFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/search/SearchNovelFragment.kt @@ -13,6 +13,7 @@ import ceui.lisa.view.NovelItemDecoration import ceui.loxia.ObjectType import ceui.loxia.RefreshHint import ceui.loxia.observeEvent +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -33,7 +34,7 @@ class SearchNovelFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) binding.radioTab.setTabs(listOf( "热度预览", "从新到旧", @@ -52,7 +53,5 @@ class SearchNovelFragment : PixivFragment(R.layout.fragment_pixiv_list) { binding.radioTab.selectTab(index) binding.usersYori.isVisible = (index == 1) || (index == 2) } - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - binding.listView.addItemDecoration(NovelItemDecoration(4.ppppx)) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/search/SearchUserFragment.kt b/app/src/main/java/ceui/pixiv/ui/search/SearchUserFragment.kt index 0c78c4055..e9a22a06f 100644 --- a/app/src/main/java/ceui/pixiv/ui/search/SearchUserFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/search/SearchUserFragment.kt @@ -17,6 +17,7 @@ import ceui.loxia.observeEvent import ceui.pixiv.ui.common.BottomDividerDecoration import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -50,9 +51,7 @@ class SearchUserFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - binding.listView.addItemDecoration(LinearItemDecoration(20.ppppx)) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) searchViewModel.searchUserEvent.observeEvent(viewLifecycleOwner) { viewModel.refresh(RefreshHint.InitialLoad) } diff --git a/app/src/main/java/ceui/pixiv/ui/settings/CookieNotSyncException.kt b/app/src/main/java/ceui/pixiv/ui/settings/CookieNotSyncException.kt new file mode 100644 index 000000000..84ec976f3 --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/settings/CookieNotSyncException.kt @@ -0,0 +1,3 @@ +package ceui.pixiv.ui.settings + +class CookieNotSyncException(msg: String) : RuntimeException(msg) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/settings/SettingsFragment.kt b/app/src/main/java/ceui/pixiv/ui/settings/SettingsFragment.kt index 104b3c2eb..b8b12ccb4 100644 --- a/app/src/main/java/ceui/pixiv/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/settings/SettingsFragment.kt @@ -10,8 +10,10 @@ import ceui.loxia.User import ceui.loxia.pushFragment import ceui.pixiv.session.SessionManager import ceui.pixiv.ui.common.CommonAdapter +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.TabCellHolder +import ceui.pixiv.ui.common.setUpLayoutManager import ceui.pixiv.ui.common.setUpToolbar import ceui.pixiv.ui.web.WebFragmentArgs import ceui.refactor.viewBinding @@ -30,8 +32,7 @@ class SettingsFragment : PixivFragment(R.layout.fragment_pixiv_list) { binding.refreshLayout.setRefreshFooter(FalsifyFooter(requireContext())) setUpToolbar(binding.toolbarLayout, binding.listView) binding.listView.adapter = adapter - val ctx = requireContext() - binding.listView.layoutManager = LinearLayoutManager(ctx) + setUpLayoutManager(binding.listView, ListMode.VERTICAL_NO_MARGIN) val liveUser = ObjectPool.get(SessionManager.loggedInUid) liveUser.observe(viewLifecycleOwner) { user -> adapter.submitList( diff --git a/app/src/main/java/ceui/pixiv/ui/task/BookmarkAllTask.kt b/app/src/main/java/ceui/pixiv/ui/task/BookmarkTask.kt similarity index 53% rename from app/src/main/java/ceui/pixiv/ui/task/BookmarkAllTask.kt rename to app/src/main/java/ceui/pixiv/ui/task/BookmarkTask.kt index 1b07635f7..4c0ee164f 100644 --- a/app/src/main/java/ceui/pixiv/ui/task/BookmarkAllTask.kt +++ b/app/src/main/java/ceui/pixiv/ui/task/BookmarkTask.kt @@ -2,19 +2,20 @@ package ceui.pixiv.ui.task import ceui.loxia.Client import ceui.loxia.ObjectType -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch class BookmarkTask( private val objectId: Long, private val objectType: String -) : QueuedRunnable() { +) : QueuedRunnable() { override suspend fun execute() { if (_status.value is TaskStatus.Executing || _status.value is TaskStatus.Finished) { + onIgnore() return } + try { + onStart() _status.value = TaskStatus.Executing(0) if (objectType == ObjectType.NOVEL) { // Client.appApi.postBookmark() @@ -22,31 +23,9 @@ class BookmarkTask( Client.appApi.postBookmark(objectId) } _status.value = TaskStatus.Finished + onEnd(Unit) } catch (ex: Exception) { - _status.value = TaskStatus.Error(ex) - ex.printStackTrace() - } finally { - optionalDelay() + onError(ex) } } } - - -class BookmarkAllTask( - contentsProvider: () -> List -) { - private val pendingTasks = mutableListOf() - - init { - val contents = contentsProvider() - pendingTasks.addAll(contents) - } - - fun go() { - MainScope().launch { - pendingTasks.forEach { - it.execute() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/CacheFileFragment.kt b/app/src/main/java/ceui/pixiv/ui/task/CacheFileFragment.kt index 3bfdc0fa0..cad9d4be9 100644 --- a/app/src/main/java/ceui/pixiv/ui/task/CacheFileFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/task/CacheFileFragment.kt @@ -5,81 +5,32 @@ import android.view.View import androidx.navigation.fragment.navArgs import ceui.lisa.R import ceui.lisa.databinding.FragmentPixivListBinding -import ceui.lisa.utils.Common -import ceui.loxia.Illust -import ceui.loxia.KListShow -import ceui.loxia.ObjectType -import ceui.pixiv.ui.common.DataSource -import ceui.pixiv.ui.common.IllustCardHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment -import ceui.pixiv.ui.common.getImageIdInGallery -import ceui.pixiv.ui.common.setUpStaggerLayout +import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel -import ceui.pixiv.ui.works.buildPixivWorksFileName import ceui.refactor.setOnClick import ceui.refactor.viewBinding import com.google.gson.Gson import com.tencent.mmkv.MMKV -import kotlinx.coroutines.delay class CacheFileFragment : PixivFragment(R.layout.fragment_pixiv_list) { private val binding by viewBinding(FragmentPixivListBinding::bind) private val args by navArgs() private val prefStore by lazy { MMKV.mmkvWithID("user-tasks") } - private val viewModel by pixivListViewModel { - DataSource( - dataFetcher = { - delay(600L) - object : KListShow { - override val displayList: List - get() = loadIllustsFromCache(args.taskUuid) ?: listOf() - override val nextPageUrl: String? - get() = null - } - }, - itemMapper = { illust -> listOf(IllustCardHolder(illust)) } - ) + private val viewModel by pixivListViewModel({ Pair(requireActivity(), args.taskUuid) }) { (activity, taskUuid) -> + QueuedTaskDataSource(taskUuid, activity) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val humanReadableTask = Gson().fromJson(prefStore.getString(args.taskUuid, ""), HumanReadableTask::class.java) binding.toolbarLayout.naviTitle.text = humanReadableTask.taskFullName - setUpStaggerLayout(binding, viewModel) binding.toolbarLayout.naviMore.setOnClick { - if (humanReadableTask.taskType == PixivTaskType.DownloadAll) { - val task = DownloadAllTask(requireActivity()) { - val items = mutableListOf() - loadIllustsFromCache(args.taskUuid)?.forEach { illust -> - if (illust.page_count == 1) { - illust.meta_single_page?.original_image_url?.let { - items.add(NamedUrl(buildPixivWorksFileName(illust.id), it)) - } - } else { - illust.meta_pages?.forEachIndexed { index, page -> - page.image_urls?.original?.let { - items.add(NamedUrl(buildPixivWorksFileName(illust.id, index), it)) - } - } - } - } - items - } - task.pendingTasks.forEach { - val isExist = getImageIdInGallery(requireContext(), it.content.name) - Common.showLog("sdaadsads2 ${it.content.name}, ${isExist}") - } - } else if (humanReadableTask.taskType == PixivTaskType.BookmarkAll) { - val task = BookmarkAllTask { - val items = mutableListOf() - loadIllustsFromCache(args.taskUuid)?.forEach { illust -> - BookmarkTask(illust.id, ObjectType.ILLUST) - } - items - } - } + TaskQueueManager.startProcessing() } + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/DownloadAllTask.kt b/app/src/main/java/ceui/pixiv/ui/task/DownloadAllTask.kt deleted file mode 100644 index 5e0e01e7a..000000000 --- a/app/src/main/java/ceui/pixiv/ui/task/DownloadAllTask.kt +++ /dev/null @@ -1,179 +0,0 @@ -package ceui.pixiv.ui.task - -import android.content.Context -import android.graphics.ColorSpace.Named -import android.net.Uri -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.lifecycleScope -import ceui.lisa.activities.Shaft -import ceui.lisa.utils.Common -import ceui.lisa.utils.GlideUrlChild -import ceui.pixiv.ui.common.saveImageToGallery -import com.bumptech.glide.Glide -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import me.jessyan.progressmanager.ProgressListener -import me.jessyan.progressmanager.ProgressManager -import me.jessyan.progressmanager.body.ProgressInfo -import java.io.File -import java.util.jar.Attributes.Name - -data class NamedUrl( - val name: String, - val url: String, -) - -class DownloadAllTask( - activity: FragmentActivity, - contentsProvider: () -> List -) { - val pendingTasks = mutableListOf() - - init { - val contents = contentsProvider() - contents.forEach { content -> - pendingTasks.add(DownloadTask(content, activity)) - } - } - - fun go() { - MainScope().launch { - pendingTasks.forEach { - it.execute() - } - } - } -} - -abstract class QueuedRunnable { - - protected val _status = MutableLiveData(TaskStatus.NotStart) - val status: LiveData = _status - - abstract suspend fun execute() - - open suspend fun optionalDelay() { - - } -} - -open class LoadTask( - val content: NamedUrl, - private val activity: FragmentActivity, - autoStart: Boolean = true -) : QueuedRunnable() { - - private val _file = MutableLiveData() - val file: LiveData = _file - - init { - if (autoStart) { - activity.lifecycleScope.launch { - execute() - } - } - } - - override suspend fun execute() { - if (_status.value is TaskStatus.Executing || _status.value is TaskStatus.Finished) { - return - } - try { - _status.value = TaskStatus.Executing(0) - ProgressManager.getInstance().addResponseListener(content.url, object : ProgressListener { - override fun onProgress(progressInfo: ProgressInfo) { - Common.showLog("dsaasdasw2 ${progressInfo.percent}") - if (progressInfo.isFinish || progressInfo.percent == 100) { - _status.value = TaskStatus.Finished - } else { - if (_status.value != TaskStatus.Finished) { - _status.value = TaskStatus.Executing(progressInfo.percent) - } - } - } - - override fun onError(id: Long, e: Exception) { - _status.value = TaskStatus.Error(e) - } - }) - - val file = withContext(Dispatchers.IO) { - Glide.with(activity) - .asFile() - .load(content.url.takeIf { - it.startsWith("http") - }?.let { GlideUrlChild(it) } ?: Uri.parse(content.url)) - .listener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target, - isFirstResource: Boolean - ): Boolean { - e?.let { - _status.postValue(TaskStatus.Error(it)) - } - return false - } - - override fun onResourceReady( - resource: File, - model: Any, - target: Target?, - dataSource: DataSource, - isFirstResource: Boolean - ): Boolean { - Common.showLog("dsaasdw2 onResourceReady ${dataSource.name} ${resource.path}") - return false - } - }) - .submit() - .get() - } - _file.value = file - onFilePrepared(file) - _status.value = TaskStatus.Finished - } catch (ex: Exception) { - _status.value = TaskStatus.Error(ex) - ex.printStackTrace() - } finally { - optionalDelay() - } - } - - open fun onFilePrepared(file: File) { - - } -} - - -class DownloadTask(content: NamedUrl, private val activity: FragmentActivity) : - LoadTask(content, activity, autoStart = false) { - - override fun onFilePrepared(file: File) { - super.onFilePrepared(file) - saveImageToGallery(activity, file, content.name) - } - - override suspend fun optionalDelay() { - super.optionalDelay() - delay(3000L) - } -} - -sealed class TaskStatus { - data object NotStart : TaskStatus() - data class Executing(val percentage: Int) : TaskStatus() - data object Finished : TaskStatus() - data class Error(val exception: Exception) : TaskStatus() -} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/DownloadTask.kt b/app/src/main/java/ceui/pixiv/ui/task/DownloadTask.kt new file mode 100644 index 000000000..f44eb920e --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/DownloadTask.kt @@ -0,0 +1,65 @@ +package ceui.pixiv.ui.task + +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import ceui.pixiv.ui.common.getImageIdInGallery +import ceui.pixiv.ui.common.saveImageToGallery +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber +import java.io.File + +class DownloadTask(content: NamedUrl, private val activity: FragmentActivity) : + LoadTask(content, activity, autoStart = false) { + + private var _onNext: (() -> Unit)? = null + + init { + Timber.d("fsaasdw2 创建了一个 DownloadTask: ${content}") + activity.lifecycleScope.launch { + val imageId = withContext(Dispatchers.IO) { + getImageIdInGallery(activity, content.name) + } + if (imageId != null) { + _status.value = TaskStatus.Finished + } + } + } + /** + * 启动任务并设置回调 + */ + fun startDownload(onNext: () -> Unit) { + this._onNext = onNext + + activity.lifecycleScope.launch { + val imageId = withContext(Dispatchers.IO) { + getImageIdInGallery(activity, content.name) + } + if (imageId != null) { + Timber.d("${content.name} 图片已存在") + delay(100L) + _status.value = TaskStatus.Finished + onNext.invoke() + } else { + Timber.d("${content.name} 图片不已存在,准备下载") + delay(400L) + execute() + } + } + } + + override fun onEnd(resultT: File) { + super.onEnd(resultT) + saveImageToGallery(activity, resultT, content.name) + this._onNext?.invoke() + } + + override fun onError(ex: Exception?) { + super.onError(ex) + if (ex != null) { + this._onNext?.invoke() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/FetchAllTask.kt b/app/src/main/java/ceui/pixiv/ui/task/FetchAllTask.kt index 6805fbd5c..689c9e887 100644 --- a/app/src/main/java/ceui/pixiv/ui/task/FetchAllTask.kt +++ b/app/src/main/java/ceui/pixiv/ui/task/FetchAllTask.kt @@ -4,15 +4,18 @@ import ceui.lisa.utils.Common import ceui.loxia.Client import ceui.loxia.Illust import ceui.loxia.KListShow +import ceui.lisa.R +import ceui.loxia.launchSuspend +import ceui.loxia.pushFragment +import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.getFileSize import com.blankj.utilcode.util.PathUtils import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.hjq.toast.ToastUtils import com.tencent.mmkv.MMKV import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.BufferedReader import java.io.BufferedWriter @@ -36,6 +39,7 @@ data class HumanReadableTask( ) open class FetchAllTask>( + private val parentFragment: PixivFragment, taskFullName: String, taskType: Int, initialLoader: suspend () -> KListShow @@ -48,7 +52,7 @@ open class FetchAllTask>( } init { - MainScope().launch { + parentFragment.launchSuspend { withContext(Dispatchers.IO) { try { if (results.isNotEmpty()) { @@ -70,7 +74,7 @@ open class FetchAllTask>( while (!nextPageUrl.isNullOrEmpty()) { val responseClass = resp::class.java Common.showLog("FetchAllTask subsequent start ${results.size}") - delay(3000L) + delay(1000L) val responseBody = Client.appApi.generalGet(nextPageUrl) val responseJson = responseBody.string() val response = gson.fromJson(responseJson, responseClass) as ResponseT @@ -97,7 +101,9 @@ open class FetchAllTask>( val fileSize = getFileSize(cacheFile) Common.showLog("FetchAllTask fileSize ${fileSize}") - onEnd(results) + withContext(Dispatchers.Main) { + onEnd(taskUUID, results) + } } catch (ex: Exception) { ex.printStackTrace() // Handle exceptions accordingly @@ -106,7 +112,9 @@ open class FetchAllTask>( } } - open fun onEnd(results: List) { + open fun onEnd(taskUUID: String, results: List) { + parentFragment.pushFragment(R.id.navigation_cache_list, CacheFileFragmentArgs(taskUuid = taskUUID).toBundle()) + ToastUtils.show("全部结束") Common.showLog("FetchAllTask all end ${this.results.size}") } } diff --git a/app/src/main/java/ceui/pixiv/ui/task/LoadTask.kt b/app/src/main/java/ceui/pixiv/ui/task/LoadTask.kt new file mode 100644 index 000000000..49b573df8 --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/LoadTask.kt @@ -0,0 +1,121 @@ +package ceui.pixiv.ui.task + +import android.net.Uri +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import ceui.lisa.utils.Common +import ceui.lisa.utils.GlideUrlChild +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.jessyan.progressmanager.ProgressListener +import me.jessyan.progressmanager.ProgressManager +import me.jessyan.progressmanager.body.ProgressInfo +import java.io.File + +open class LoadTask( + val content: NamedUrl, + private val activity: FragmentActivity, + autoStart: Boolean = true +) : QueuedRunnable() { + + override val taskId: Long + get() = content.url.hashCode().toLong() + + init { + if (autoStart) { + activity.lifecycleScope.launch { + execute() + } + } + } + + override suspend fun execute() { + // 防止重复执行任务 + if (_status.value !is TaskStatus.NotStart) { + onIgnore() + return + } + + try { + onStart() + _status.value = TaskStatus.Executing(0) + observeProgress() + + val file = downloadFile() + if (file != null) { + _result.value = file + _status.value = TaskStatus.Finished + onEnd(file) + } else { + throw IllegalStateException("Unexpected null file") + } + } catch (ex: Exception) { + onError(ex) + } + } + + private fun observeProgress() { + ProgressManager.getInstance().addResponseListener(content.url, object : ProgressListener { + override fun onProgress(progressInfo: ProgressInfo) { + val percent = progressInfo.percent + if (progressInfo.isFinish || percent == 100) { + _status.value = TaskStatus.Finished + } else { + if (_status.value != TaskStatus.Finished) { + _status.value = TaskStatus.Executing(percent) + } + } + } + + override fun onError(id: Long, ex: Exception) { + onError(ex) + } + }) + } + + private suspend fun downloadFile(): File? { + return withContext(Dispatchers.IO) { + Glide.with(activity) + .asFile() + .load( + content.url.takeIf { it.startsWith("http") } + ?.let { GlideUrlChild(it) } + ?: Uri.parse(content.url) + ) + .listener(createGlideListener()) + .submit() + .get() + } + } + + private fun createGlideListener(): RequestListener { + return object : RequestListener { + override fun onLoadFailed( + ex: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { + onError(ex) + return false + } + + override fun onResourceReady( + resource: File, + model: Any, + target: Target?, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + Common.showLog("Resource ready: ${dataSource.name}, path: ${resource.path}") + return false + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/NamedUrl.kt b/app/src/main/java/ceui/pixiv/ui/task/NamedUrl.kt new file mode 100644 index 000000000..a9639d8fa --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/NamedUrl.kt @@ -0,0 +1,6 @@ +package ceui.pixiv.ui.task + +data class NamedUrl( + val name: String, + val url: String, +) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/QueuedRunnable.kt b/app/src/main/java/ceui/pixiv/ui/task/QueuedRunnable.kt new file mode 100644 index 000000000..5cc33318b --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/QueuedRunnable.kt @@ -0,0 +1,47 @@ +package ceui.pixiv.ui.task + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.map +import timber.log.Timber +import java.io.File +import java.util.UUID + +abstract class QueuedRunnable { + + protected val _status = MutableLiveData(TaskStatus.NotStart) + val status: LiveData = _status + + protected val _result = MutableLiveData() + val result: LiveData get() = _result + + open val taskId = UUID.randomUUID().hashCode().toLong() + + val isDownloading: LiveData = status.map { it is TaskStatus.Executing } + + abstract suspend fun execute() + + fun reset() { + _status.value = TaskStatus.NotStart + } + + open fun onIgnore() { + Timber.d("${this.javaClass.simpleName}-${taskId} empty onIgnore") + } + + open fun onStart() { + Timber.d("${this.javaClass.simpleName}-${taskId} onStart") + } + + open fun onEnd(resultT: ResultT) { + Timber.d("${this.javaClass.simpleName}-${taskId} onEnd") + } + + open fun onError(ex: Exception?) { + Timber.d("${this.javaClass.simpleName}-${taskId} handleError") + if (ex != null) { + Timber.e(ex) + _status.postValue(TaskStatus.Error(ex)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/QueuedTaskDataSource.kt b/app/src/main/java/ceui/pixiv/ui/task/QueuedTaskDataSource.kt new file mode 100644 index 000000000..a981bf6aa --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/QueuedTaskDataSource.kt @@ -0,0 +1,38 @@ +package ceui.pixiv.ui.task + +import androidx.fragment.app.FragmentActivity +import ceui.loxia.Illust +import ceui.loxia.KListShow +import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.works.buildPixivWorksFileName + +class QueuedTaskDataSource( + private val taskUuid: String, private val activity: FragmentActivity +) : DataSource>(dataFetcher = { + object : KListShow { + override val displayList: List + get() = loadIllustsFromCache(taskUuid) ?: listOf() + override val nextPageUrl: String? + get() = null + } +}, responseStore = null, itemMapper = { illust -> + val items = mutableListOf() + if (illust.page_count == 1) { + illust.meta_single_page?.original_image_url?.let { + items.add(NamedUrl(buildPixivWorksFileName(illust.id), it)) + } + } else { + illust.meta_pages?.forEachIndexed { index, page -> + page.image_urls?.original?.let { + items.add(NamedUrl(buildPixivWorksFileName(illust.id, index), it)) + } + } + } + items.map { + val taskId = it.url.hashCode().toLong() + val task = TaskQueueManager.findExistingTask(taskId) ?: DownloadTask(it, activity).also { + TaskQueueManager.addTask(it) + } + task + }.map { QueuedTaskHolder(it, illust) } +}) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/QueuedTaskHolder.kt b/app/src/main/java/ceui/pixiv/ui/task/QueuedTaskHolder.kt new file mode 100644 index 000000000..8c3029899 --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/QueuedTaskHolder.kt @@ -0,0 +1,73 @@ +package ceui.pixiv.ui.task + +import android.graphics.Color +import android.os.Build +import android.view.View +import android.widget.ProgressBar +import android.widget.TextView +import androidx.databinding.BindingAdapter +import androidx.lifecycle.LiveData +import ceui.lisa.R +import ceui.lisa.annotations.ItemHolder +import ceui.lisa.databinding.CellQueuedTaskBinding +import ceui.lisa.databinding.CellUsersYoruItemBinding +import ceui.loxia.Illust +import ceui.loxia.findActionReceiverOrNull +import ceui.pixiv.ui.bottom.UsersYoriActionReceiver +import ceui.pixiv.ui.common.ListItemHolder +import ceui.pixiv.ui.common.ListItemViewHolder + +class QueuedTaskHolder(val downloadTask: DownloadTask, val illust: Illust) : ListItemHolder() { + override fun getItemId(): Long { + return downloadTask.taskId + } +} + +@ItemHolder(QueuedTaskHolder::class) +class QueuedTaskViewHolder(bd: CellQueuedTaskBinding) : + ListItemViewHolder(bd) { + + override fun onBindViewHolder(holder: QueuedTaskHolder, position: Int) { + super.onBindViewHolder(holder, position) + binding.task = holder.downloadTask + } +} + +@BindingAdapter("status_desc") +fun TextView.binding_setStatusDesc(taskStatus: TaskStatus?) { + if (taskStatus != null) { + if (taskStatus is TaskStatus.NotStart) { + text = "未开始" + setTextColor(Color.parseColor("#FFB332")) + } else if (taskStatus is TaskStatus.Executing) { + text = "下载中 - ${taskStatus.percentage}%" + setTextColor(Color.parseColor("#00FF94")) + } else if (taskStatus is TaskStatus.Finished) { + text = "完成了" + setTextColor(Color.parseColor("#00FF94")) + } else if (taskStatus is TaskStatus.Error) { + text = "出错了" + setTextColor(Color.parseColor("#FFB332")) + } else { + text = "taskStatus unknown" + } + } else { + text = "taskStatus null" + } +} + +@BindingAdapter("status_percentage") +fun ProgressBar.binding_setStatusPercentage(taskStatus: TaskStatus?) { + if (taskStatus != null) { + if (taskStatus is TaskStatus.Executing) { + // 兼容不同版本的 setProgress 调用 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // 对于 Android 7.0 及以上版本,使用 setProgress 的第二个参数进行动画 + setProgress(taskStatus.percentage, true) + } else { + // 对于 Android 7.0 以下的版本,使用 setProgress 不带动画 + progress = taskStatus.percentage + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/TaskDataSource.kt b/app/src/main/java/ceui/pixiv/ui/task/TaskDataSource.kt deleted file mode 100644 index f26f22235..000000000 --- a/app/src/main/java/ceui/pixiv/ui/task/TaskDataSource.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ceui.pixiv.ui.task - -import ceui.pixiv.ui.common.DataSource diff --git a/app/src/main/java/ceui/pixiv/ui/task/TaskListFragment.kt b/app/src/main/java/ceui/pixiv/ui/task/TaskListFragment.kt index 4db2c07c8..23316d4ef 100644 --- a/app/src/main/java/ceui/pixiv/ui/task/TaskListFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/task/TaskListFragment.kt @@ -16,6 +16,7 @@ import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.ListItemHolder import ceui.pixiv.ui.common.ListItemViewHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -43,7 +44,7 @@ class TaskListFragment : PixivFragment(R.layout.fragment_pixiv_list), TaskPrevie object : KListShow { override val displayList: List - get() = humanReadableTasks + get() = humanReadableTasks.sortedByDescending { it.createdTime } override val nextPageUrl: String? get() = null } @@ -56,9 +57,7 @@ class TaskListFragment : PixivFragment(R.layout.fragment_pixiv_list), TaskPrevie override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) - binding.toolbarLayout.naviTitle.text = getString(R.string.created_tasks) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) } override fun onClickTaskPreview(humanReadableTask: HumanReadableTask) { diff --git a/app/src/main/java/ceui/pixiv/ui/task/TaskQueueManager.kt b/app/src/main/java/ceui/pixiv/ui/task/TaskQueueManager.kt new file mode 100644 index 000000000..40ea01693 --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/TaskQueueManager.kt @@ -0,0 +1,155 @@ +package ceui.pixiv.ui.task + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import timber.log.Timber + +object TaskQueueManager { + + enum class TaskState { + IDLE, // Idle state + RUNNING, // Task is running + PAUSED // Task is paused + } + + private val taskQueue: MutableList = mutableListOf() + private val lock = Any() + + private val _taskState = MutableLiveData(TaskState.IDLE) + val taskState: LiveData get() = _taskState + + fun addTask(task: DownloadTask) { + synchronized(lock) { + if (!isTaskInQueue(task)) { + taskQueue.add(task) + Timber.d("Task added: ${task.taskId}") + updateTaskState() + } else { + Timber.d("Task already in queue: ${task.taskId}") + } + } + } + + fun addTasks(tasks: List) { + synchronized(lock) { + val newTasks = tasks.filterNot { isTaskInQueue(it) } + if (newTasks.isNotEmpty()) { + taskQueue.addAll(newTasks) + Timber.d("Tasks added: ${newTasks.map { it.taskId }}") + updateTaskState() + } + } + } + + private fun isTaskInQueue(task: DownloadTask): Boolean { + return taskQueue.any { it.taskId == task.taskId } + } + + fun startProcessing() { + synchronized(lock) { + if (_taskState.value != TaskState.RUNNING) { + Timber.d("Starting task processing") + _taskState.value = TaskState.RUNNING + } else { + Timber.d("Task processing already running") + return + } + } + processNextTask() + } + + fun pauseProcessing() { + synchronized(lock) { + if (_taskState.value == TaskState.RUNNING) { + Timber.d("Pausing task processing") + _taskState.value = TaskState.PAUSED + } else { + Timber.d("Cannot pause, current state: ${_taskState.value}") + } + } + } + + private fun processNextTask() { + val currentTask: DownloadTask? + synchronized(lock) { + if (_taskState.value != TaskState.RUNNING) { + Timber.d("Processing not running, current state: ${_taskState.value}") + return + } + + currentTask = taskQueue.firstOrNull { it.status.value is TaskStatus.NotStart } + if (currentTask == null) { + if (taskQueue.all { it.status.value is TaskStatus.Finished }) { + Timber.d("All tasks completed") + _taskState.value = TaskState.IDLE + } else { + retryFailedTasks() + } + return + } + } + + currentTask?.startDownload { + handleTaskCompletion() + } + } + + private fun handleTaskCompletion() { + synchronized(lock) { + if (taskQueue.all { it.status.value is TaskStatus.Finished }) { + Timber.d("All tasks completed") + _taskState.value = TaskState.IDLE + } + } + processNextTask() + } + + /** + * Retry failed tasks + */ + private fun retryFailedTasks() { + var shouldProcess = false + synchronized(lock) { + val failedTasks = taskQueue.filter { it.status.value is TaskStatus.Error } + if (failedTasks.isNotEmpty()) { + Timber.d("Retrying failed tasks: ${failedTasks.size}") + failedTasks.forEach { task -> + task.reset() + } + shouldProcess = true + } else { + shouldProcess = false + Timber.d("No failed tasks to retry") + } + } + if (shouldProcess) { + processNextTask() + } + } + + fun clearAllTasks() { + synchronized(lock) { + taskQueue.clear() + _taskState.value = TaskState.IDLE + Timber.d("All tasks cleared") + } + } + + private fun updateTaskState() { + synchronized(lock) { + _taskState.value = if (taskQueue.any { it.status.value !is TaskStatus.Finished }) { + TaskState.IDLE + } else { + TaskState.IDLE + } + } + } + + fun findExistingTask(taskId: Long): DownloadTask? { + synchronized(lock) { + val task = taskQueue.firstOrNull { it.taskId == taskId } + Timber.d(if (task != null) "Task found: $taskId" else "Task not found: $taskId") + return task + } + } +} diff --git a/app/src/main/java/ceui/pixiv/ui/task/TaskStatus.kt b/app/src/main/java/ceui/pixiv/ui/task/TaskStatus.kt new file mode 100644 index 000000000..2231152e8 --- /dev/null +++ b/app/src/main/java/ceui/pixiv/ui/task/TaskStatus.kt @@ -0,0 +1,8 @@ +package ceui.pixiv.ui.task + +sealed class TaskStatus { + data object NotStart : TaskStatus() + data class Executing(val percentage: Int) : TaskStatus() + data object Finished : TaskStatus() + data class Error(val exception: Exception) : TaskStatus() +} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/task/TaskStatusFragment.kt b/app/src/main/java/ceui/pixiv/ui/task/TaskStatusFragment.kt index e8055bdf2..96333ecd5 100644 --- a/app/src/main/java/ceui/pixiv/ui/task/TaskStatusFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/task/TaskStatusFragment.kt @@ -19,26 +19,5 @@ class TaskStatusFragment : PixivFragment(R.layout.fragment_pixiv_list) { super.onViewCreated(view, savedInstanceState) setUpToolbar(binding.toolbarLayout, binding.refreshLayout) -// val task = DownloadAllTask(requireActivity()) { -// val items = mutableListOf() -// loadIllustsFromCache()?.forEach { illust -> -// if (illust.page_count == 1) { -// illust.meta_single_page?.original_image_url?.let { -// items.add(NamedUrl(buildPixivWorksFileName(illust.id), it)) -// } -// } else { -// illust.meta_pages?.forEachIndexed { index, page -> -// page.image_urls?.original?.let { -// items.add(NamedUrl(buildPixivWorksFileName(illust.id, index), it)) -// } -// } -// } -// } -// items -// } -// val adapter = CommonAdapter(viewLifecycleOwner) -// binding.listView.layoutManager = LinearLayoutManager(requireContext()) -// binding.listView.adapter = adapter -// adapter.submitList(task.pendingTasks.map { TaskStatusHolder(it) }) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsDataSource.kt b/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsDataSource.kt index 5e5eebad6..ac9b64d1c 100644 --- a/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsDataSource.kt +++ b/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsDataSource.kt @@ -5,16 +5,12 @@ import ceui.loxia.TrendingTag import ceui.loxia.TrendingTagsResponse import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.ResponseStore +import ceui.pixiv.ui.common.createResponseStore class TrendingTagsDataSource( private val args: TrendingTagsFragmentArgs, - private val responseStore: ResponseStore = ResponseStore( - { "trending-tags-${args.objectType}-api" }, - 1800 * 1000L, - TrendingTagsResponse::class.java, - { Client.appApi.trendingTags(args.objectType) } - ) ) : DataSource( - dataFetcher = { responseStore.retrieveData() }, + dataFetcher = { hint -> Client.appApi.trendingTags(args.objectType) }, + responseStore = createResponseStore({ "trending-tags-${args.objectType}-api" }), itemMapper = { trendingTag -> listOf(TrendingTagHolder(trendingTag)) } ) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsFragment.kt b/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsFragment.kt index 6e9a454a4..f8c09ab49 100644 --- a/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/trending/TrendingTagsFragment.kt @@ -16,9 +16,9 @@ import ceui.loxia.pushFragment import ceui.pixiv.ui.common.CommonAdapter import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState -import ceui.pixiv.ui.common.setUpStaggerLayout import ceui.pixiv.ui.list.pixivListViewModel import ceui.pixiv.ui.search.SearchViewPagerFragment import ceui.pixiv.ui.search.SearchViewPagerFragmentArgs @@ -34,8 +34,7 @@ class TrendingTagsFragment : PixivFragment(R.layout.fragment_pixiv_list), Trendi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) - binding.listView.layoutManager = GridLayoutManager(requireContext(), 3) + setUpRefreshState(binding, viewModel, ListMode.GRID) } override fun onClickTrendingTag(trendingTag: TrendingTag) { diff --git a/app/src/main/java/ceui/pixiv/ui/user/MineProfileFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/MineProfileFragment.kt index 5d915f9be..5a1642920 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/MineProfileFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/MineProfileFragment.kt @@ -2,42 +2,29 @@ package ceui.pixiv.ui.user import android.os.Bundle import android.view.View -import androidx.lifecycle.map import androidx.recyclerview.widget.LinearLayoutManager import ceui.lisa.R -import ceui.lisa.databinding.FragmentMineProfileBinding import ceui.lisa.databinding.FragmentPixivListBinding -import ceui.lisa.utils.Common import ceui.loxia.Client -import ceui.loxia.Illust import ceui.loxia.ObjectPool -import ceui.loxia.RefreshHint import ceui.loxia.User -import ceui.loxia.filterNull import ceui.loxia.pushFragment import ceui.pixiv.session.SessionManager import ceui.pixiv.ui.common.CommonAdapter import ceui.pixiv.ui.common.CommonViewPagerFragmentArgs -import ceui.pixiv.ui.task.DownloadAllTask -import ceui.pixiv.ui.task.NamedUrl -import ceui.pixiv.ui.task.loadIllustsFromCache +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.TabCellHolder import ceui.pixiv.ui.common.ViewPagerContentType import ceui.pixiv.ui.common.pixivValueViewModel import ceui.pixiv.ui.common.setUpRefreshState -import ceui.pixiv.ui.common.setUpToolbar -import ceui.refactor.setOnClick import ceui.refactor.viewBinding -import com.scwang.smart.refresh.header.FalsifyFooter -import com.scwang.smart.refresh.header.FalsifyHeader -import com.scwang.smart.refresh.header.MaterialHeader class MineProfileFragment : PixivFragment(R.layout.fragment_pixiv_list) { private val binding by viewBinding(FragmentPixivListBinding::bind) private val viewModel by pixivValueViewModel( - loader = { + dataFetcher = { val resp = Client.appApi.getUserProfile(SessionManager.loggedInUid) resp.user?.let { ObjectPool.update(it) @@ -51,9 +38,7 @@ class MineProfileFragment : PixivFragment(R.layout.fragment_pixiv_list) { super.onViewCreated(view, savedInstanceState) val adapter = CommonAdapter(viewLifecycleOwner) binding.listView.adapter = adapter - val ctx = requireContext() - binding.listView.layoutManager = LinearLayoutManager(ctx) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL_NO_MARGIN) val liveUser = ObjectPool.get(SessionManager.loggedInUid) liveUser.observe(viewLifecycleOwner) { user -> adapter.submitList( diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedIllustsFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedIllustsFragment.kt index e50c2832e..f6036e2e3 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedIllustsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedIllustsFragment.kt @@ -18,15 +18,10 @@ import ceui.pixiv.session.SessionManager import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.list.pixivListViewModel -import ceui.pixiv.ui.common.setUpStaggerLayout import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.TitledViewPagerFragment import ceui.pixiv.ui.common.pixivValueViewModel -import ceui.pixiv.ui.common.setUpSizedList -import ceui.pixiv.ui.task.FetchAllTask -import ceui.pixiv.ui.task.HumanReadableTask -import ceui.pixiv.ui.task.PixivTaskType -import ceui.refactor.setOnClick +import ceui.pixiv.ui.common.setUpRefreshState import ceui.refactor.viewBinding class UserBookmarkedIllustsFragment : PixivFragment(R.layout.fragment_pixiv_list) { @@ -64,6 +59,6 @@ class UserBookmarkedIllustsFragment : PixivFragment(R.layout.fragment_pixiv_list } } } - setUpStaggerLayout(binding, viewModel) + setUpRefreshState(binding, viewModel) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedNovelFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedNovelFragment.kt index 4f7058cb4..d2a1f2d72 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedNovelFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserBookmarkedNovelFragment.kt @@ -11,6 +11,7 @@ import ceui.lisa.view.LinearItemDecoration import ceui.lisa.view.NovelItemDecoration import ceui.loxia.Client import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.NovelCardHolder import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.PvisionCardHolder @@ -31,8 +32,6 @@ class UserBookmarkedNovelFragment : PixivFragment(R.layout.fragment_pixiv_list) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - binding.listView.addItemDecoration(NovelItemDecoration(10.ppppx)) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserCreatedIllustsFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserCreatedIllustsFragment.kt index f5227b1e6..0758ab5ec 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserCreatedIllustsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserCreatedIllustsFragment.kt @@ -17,11 +17,11 @@ import ceui.pixiv.ui.bottom.OffsetPageActionReceiver import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.list.pixivListViewModel -import ceui.pixiv.ui.common.setUpStaggerLayout import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.ViewPagerFragment import ceui.pixiv.ui.common.pixivValueViewModel -import ceui.pixiv.ui.common.setUpSizedList +import ceui.pixiv.ui.common.setUpRefreshState + import ceui.pixiv.widgets.DialogViewModel import ceui.refactor.viewBinding @@ -38,13 +38,6 @@ class UserCreatedIllustsFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpStaggerLayout(binding, viewModel) - ObjectPool.get(args.userId).observe(viewLifecycleOwner) { user -> - if (args.objectType == ObjectType.ILLUST) { - setUpSizedList(binding, viewModel, user.profile?.total_illusts ?: 0) - } else if (args.objectType == ObjectType.MANGA) { - setUpSizedList(binding, viewModel, user.profile?.total_manga ?: 0) - } - } + setUpRefreshState(binding, viewModel) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserCreatedNovelFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserCreatedNovelFragment.kt index 05ba92d22..005317bf6 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserCreatedNovelFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserCreatedNovelFragment.kt @@ -11,6 +11,7 @@ import ceui.lisa.view.LinearItemDecoration import ceui.lisa.view.NovelItemDecoration import ceui.loxia.Client import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.NovelCardHolder import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.PvisionCardHolder @@ -31,8 +32,6 @@ class UserCreatedNovelFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - binding.listView.addItemDecoration(NovelItemDecoration(10.ppppx)) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserFansFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserFansFragment.kt index 5472cf3a0..bc1a5027b 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserFansFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserFansFragment.kt @@ -9,6 +9,7 @@ import ceui.lisa.databinding.FragmentPixivListBinding import ceui.lisa.view.LinearItemDecoration import ceui.loxia.Client import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -28,9 +29,7 @@ class UserFansFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) binding.toolbarLayout.naviTitle.text = getString(R.string.string_322) - binding.listView.addItemDecoration(LinearItemDecoration(20.ppppx)) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserFollowingFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserFollowingFragment.kt index 5ac4e995e..d491a95d6 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserFollowingFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserFollowingFragment.kt @@ -24,12 +24,11 @@ import ceui.pixiv.session.SessionManager import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.list.pixivListViewModel -import ceui.pixiv.ui.common.setUpStaggerLayout import ceui.pixiv.ui.common.IllustCardHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.TitledViewPagerFragment import ceui.pixiv.ui.common.pixivValueViewModel import ceui.pixiv.ui.common.setUpRefreshState -import ceui.pixiv.ui.common.setUpSizedList import ceui.refactor.ppppx import ceui.refactor.viewBinding import com.bumptech.glide.Glide @@ -61,7 +60,7 @@ class UserFollowingFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) if (args.userId == SessionManager.loggedInUid) { if (args.restrictType == Params.TYPE_PUBLIC) { ObjectPool.get(args.userId).observe(viewLifecycleOwner) { user -> @@ -69,7 +68,6 @@ class UserFollowingFragment : PixivFragment(R.layout.fragment_pixiv_list) { it.getTitleLiveData(0).value = "${getString(R.string.string_391)} (${user.profile?.total_follow_users ?: 0})" } - setUpSizedList(binding, viewModel, user.profile?.total_follow_users ?: 0) } } else if (args.restrictType == Params.TYPE_PRIVATE) { contentViewModel.result.observe(viewLifecycleOwner) { result -> @@ -77,12 +75,9 @@ class UserFollowingFragment : PixivFragment(R.layout.fragment_pixiv_list) { it.getTitleLiveData(1).value = "${getString(R.string.string_392)} (${result.body?.total ?: 0})" } - setUpSizedList(binding, viewModel, result.body?.total ?: 0) } } } - binding.listView.addItemDecoration(LinearItemDecoration(20.ppppx)) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) } } diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserFriendsFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserFriendsFragment.kt index fd7f2d205..5cc36cf9c 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserFriendsFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserFriendsFragment.kt @@ -9,6 +9,7 @@ import ceui.lisa.databinding.FragmentPixivListBinding import ceui.lisa.view.LinearItemDecoration import ceui.loxia.Client import ceui.pixiv.ui.common.DataSource +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -28,9 +29,7 @@ class UserFriendsFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) binding.toolbarLayout.naviTitle.text = getString(R.string.string_323) - binding.listView.addItemDecoration(LinearItemDecoration(20.ppppx)) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserPostHolder.kt b/app/src/main/java/ceui/pixiv/ui/user/UserPostHolder.kt index abe751283..b8d3651a4 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserPostHolder.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserPostHolder.kt @@ -1,5 +1,6 @@ package ceui.pixiv.ui.user +import androidx.core.view.updateLayoutParams import ceui.lisa.annotations.ItemHolder import ceui.lisa.databinding.CellUserPostBinding import ceui.loxia.DateParse @@ -10,7 +11,11 @@ import ceui.loxia.findActionReceiverOrNull import ceui.pixiv.ui.common.IllustCardActionReceiver import ceui.pixiv.ui.common.ListItemHolder import ceui.pixiv.ui.common.ListItemViewHolder +import ceui.refactor.ppppx +import ceui.refactor.screenWidth import ceui.refactor.setOnClick +import timber.log.Timber +import kotlin.math.roundToInt class UserPostHolder(val illust: Illust) : ListItemHolder() { init { @@ -35,6 +40,17 @@ class UserPostViewHolder(bd: CellUserPostBinding) : it.findActionReceiverOrNull() ?.onClickIllustCard(holder.illust) } + val imageView = binding.image + val w = if (holder.illust.width > holder.illust.height) { + screenWidth - 36.ppppx + } else { + (screenWidth * 0.65F).roundToInt() + } + val h = (w * holder.illust.height.toFloat() / holder.illust.width.toFloat()).roundToInt() + imageView.updateLayoutParams { + width = w + height = h + } binding.postTime.text = DateParse.getTimeAgo(context, holder.illust.create_date) binding.user = ObjectPool.get(holder.illust.user?.id ?: 0L) binding.holder = holder diff --git a/app/src/main/java/ceui/pixiv/ui/user/UserProfileFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/UserProfileFragment.kt index 2d6f110bf..23fa29b9e 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/UserProfileFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/UserProfileFragment.kt @@ -13,7 +13,6 @@ import ceui.lisa.R import ceui.lisa.activities.followUser import ceui.lisa.activities.unfollowUser import ceui.lisa.databinding.FragmentUserProfileBinding -import ceui.lisa.utils.Common import ceui.lisa.utils.GlideUrlChild import ceui.lisa.utils.Params import ceui.loxia.Client @@ -45,7 +44,7 @@ class UserProfileFragment : TitledViewPagerFragment(R.layout.fragment_user_profi private val binding by viewBinding(FragmentUserProfileBinding::bind) private val args by navArgs() private val viewModel by pixivValueViewModel( - loader = { + dataFetcher = { val resp = Client.appApi.getUserProfile(args.userId) resp.user?.let { ObjectPool.update(it) @@ -123,7 +122,7 @@ class UserProfileFragment : TitledViewPagerFragment(R.layout.fragment_user_profi showActionMenu { add( MenuItem("下载全部作品", "实验性功能,测试中") { - FetchAllTask(taskFullName = "下载${ObjectPool.get(args.userId).value?.name}创作的全部插画", PixivTaskType.DownloadAll) { + FetchAllTask(this@UserProfileFragment, taskFullName = "下载${ObjectPool.get(args.userId).value?.name}创作的全部插画", PixivTaskType.DownloadAll) { Client.appApi.getUserCreatedIllusts( args.userId, ObjectType.ILLUST @@ -133,7 +132,7 @@ class UserProfileFragment : TitledViewPagerFragment(R.layout.fragment_user_profi ) add( MenuItem("收藏全部作品", "实验性功能,测试中") { - FetchAllTask(taskFullName = "收藏${ObjectPool.get(args.userId).value?.name}创作的全部插画", PixivTaskType.BookmarkAll) { + FetchAllTask(this@UserProfileFragment, taskFullName = "收藏${ObjectPool.get(args.userId).value?.name}创作的全部插画", PixivTaskType.BookmarkAll) { Client.appApi.getUserCreatedIllusts( args.userId, ObjectType.ILLUST diff --git a/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostFragment.kt index f775eaf10..cb73f1b6a 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostFragment.kt @@ -12,6 +12,7 @@ import ceui.loxia.clearItemDecorations import ceui.loxia.launchSuspend import ceui.pixiv.ui.common.CommonAdapter import ceui.pixiv.ui.common.IllustCardHolder +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -28,7 +29,6 @@ class FollowingPostFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostsDataSource.kt b/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostsDataSource.kt index 6562147bc..f2dafd1db 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostsDataSource.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/following/FollowingPostsDataSource.kt @@ -11,19 +11,15 @@ import ceui.loxia.UserPreviewResponse import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.ResponseStore +import ceui.pixiv.ui.common.createResponseStore import ceui.pixiv.ui.user.UserPostHolder import ceui.pixiv.ui.user.UserPreviewHolder class FollowingPostsDataSource( private val args: FollowingPostFragmentArgs, - private val responseStore: ResponseStore = ResponseStore( - { "following-user-${args.objectType}-api-${args.restrictType}" }, - 1800 * 1000L, - IllustResponse::class.java, - { Client.appApi.followUserPosts(args.objectType, args.restrictType ?: Params.TYPE_ALL) } - ) ) : DataSource( - dataFetcher = { responseStore.retrieveData() }, + dataFetcher = { hint -> Client.appApi.followUserPosts(args.objectType, args.restrictType ?: Params.TYPE_ALL) }, + responseStore = createResponseStore({ "following-user-${args.objectType}-api-${args.restrictType}" }), itemMapper = { illust -> listOf(UserPostHolder(illust)) }, filter = { illust -> illust.isAuthurExist() } ) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersDataSource.kt b/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersDataSource.kt index 73f2f923c..de215b9c1 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersDataSource.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersDataSource.kt @@ -8,16 +8,11 @@ import ceui.loxia.UserPreviewResponse import ceui.pixiv.ui.common.DataSource import ceui.pixiv.ui.common.IllustCardHolder import ceui.pixiv.ui.common.ResponseStore +import ceui.pixiv.ui.common.createResponseStore import ceui.pixiv.ui.user.UserPreviewHolder -class RecommendUsersDataSource( - private val responseStore: ResponseStore = ResponseStore( - { "recommend-users-api" }, - 1800 * 1000L, - UserPreviewResponse::class.java, - { Client.appApi.recommendedUsers() } - ) -) : DataSource( - dataFetcher = { responseStore.retrieveData() }, +class RecommendUsersDataSource : DataSource( + dataFetcher = { hint -> Client.appApi.recommendedUsers() }, + responseStore = createResponseStore({ "recommend-users-api" }), itemMapper = { preview -> listOf(UserPreviewHolder(preview)) } ) \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersFragment.kt b/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersFragment.kt index e14fa3359..3e7ebffdb 100644 --- a/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/user/recommend/RecommendUsersFragment.kt @@ -7,6 +7,7 @@ import ceui.lisa.R import ceui.lisa.databinding.FragmentPixivListBinding import ceui.lisa.view.LinearItemDecoration import ceui.pixiv.ui.common.BottomDividerDecoration +import ceui.pixiv.ui.common.ListMode import ceui.pixiv.ui.common.PixivFragment import ceui.pixiv.ui.common.setUpRefreshState import ceui.pixiv.ui.list.pixivListViewModel @@ -22,8 +23,6 @@ class RecommendUsersFragment : PixivFragment(R.layout.fragment_pixiv_list) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.listView.layoutManager = LinearLayoutManager(requireContext()) - binding.listView.addItemDecoration(LinearItemDecoration(20.ppppx)) - setUpRefreshState(binding, viewModel) + setUpRefreshState(binding, viewModel, ListMode.VERTICAL) } } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/ui/web/WebFragment.kt b/app/src/main/java/ceui/pixiv/ui/web/WebFragment.kt index 789348f26..661580c7d 100644 --- a/app/src/main/java/ceui/pixiv/ui/web/WebFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/web/WebFragment.kt @@ -20,7 +20,9 @@ import androidx.navigation.fragment.navArgs import ceui.lisa.R import ceui.lisa.databinding.FragmentWebBinding import ceui.lisa.utils.Common +import ceui.pixiv.session.SessionManager import ceui.pixiv.ui.common.PixivFragment +import ceui.pixiv.ui.settings.SettingsFragment import ceui.refactor.viewBinding import com.scwang.smart.refresh.header.FalsifyFooter import com.scwang.smart.refresh.header.MaterialHeader @@ -78,7 +80,7 @@ class WebFragment : PixivFragment(R.layout.fragment_web) { url?.let { val cookie = CookieManager.getInstance().getCookie(it) Common.showLog("dsaadsdsaaww2 set ${cookie}") - prefStore.putString("web-api-cookie", cookie) + prefStore.putString(SessionManager.COOKIE_KEY, cookie) } } diff --git a/app/src/main/java/ceui/pixiv/ui/works/GalleryHolder.kt b/app/src/main/java/ceui/pixiv/ui/works/GalleryHolder.kt index 769ef9569..15a7e7136 100644 --- a/app/src/main/java/ceui/pixiv/ui/works/GalleryHolder.kt +++ b/app/src/main/java/ceui/pixiv/ui/works/GalleryHolder.kt @@ -1,28 +1,18 @@ package ceui.pixiv.ui.works import androidx.core.view.updateLayoutParams -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import ceui.lisa.annotations.ItemHolder import ceui.lisa.databinding.CellGalleryBinding -import ceui.lisa.utils.GlideUrlChild import ceui.loxia.Illust -import ceui.loxia.MetaPage -import ceui.loxia.findActionReceiver import ceui.loxia.findActionReceiverOrNull import ceui.pixiv.ui.common.ListItemHolder import ceui.pixiv.ui.common.ListItemViewHolder import ceui.pixiv.ui.common.getImageDimensions import ceui.pixiv.ui.common.setUpWithTaskStatus import ceui.pixiv.ui.task.LoadTask -import ceui.pixiv.ui.task.TaskStatus -import ceui.refactor.ppppx import ceui.refactor.screenWidth import ceui.refactor.setOnClick -import com.bumptech.glide.Glide -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.github.panpf.sketch.loadImage -import java.io.File import kotlin.math.roundToInt class GalleryHolder( @@ -49,7 +39,7 @@ class GalleryViewHolder(bd: CellGalleryBinding) : super.onBindViewHolder(holder, position) holder.loadUrl() lifecycleOwner?.let { - holder.loadTask.file.observe(it) { file -> + holder.loadTask.result.observe(it) { file -> val resolution = getImageDimensions(file) val imgHeight = (screenWidth * resolution.second / resolution.first.toFloat()).roundToInt() diff --git a/app/src/main/java/ceui/pixiv/ui/works/IllustFragment.kt b/app/src/main/java/ceui/pixiv/ui/works/IllustFragment.kt index bffef0024..79bd6fec8 100644 --- a/app/src/main/java/ceui/pixiv/ui/works/IllustFragment.kt +++ b/app/src/main/java/ceui/pixiv/ui/works/IllustFragment.kt @@ -2,6 +2,7 @@ package ceui.pixiv.ui.works import android.os.Bundle import android.view.View +import androidx.core.text.HtmlCompat import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope @@ -19,13 +20,13 @@ import ceui.loxia.Illust import ceui.loxia.ObjectPool import ceui.loxia.ObjectType import ceui.loxia.User -import ceui.loxia.launchSuspend import ceui.loxia.pushFragment import ceui.pixiv.ui.comments.CommentsFragmentArgs import ceui.pixiv.ui.common.CommonAdapter import ceui.pixiv.ui.common.ImgDisplayFragment import ceui.pixiv.ui.common.pixivValueViewModel import ceui.pixiv.ui.common.setUpFullScreen +import ceui.pixiv.ui.related.RelatedIllustsFragmentArgs import ceui.pixiv.ui.task.NamedUrl import ceui.pixiv.ui.task.TaskPool import ceui.pixiv.ui.user.UserProfileFragmentArgs @@ -50,7 +51,7 @@ class IllustFragment : ImgDisplayFragment(R.layout.fragment_fancy_illust), Galle get() = binding.image private val liveIllust by lazy { ObjectPool.get(args.illustId) } - private val pixivViewModel by pixivValueViewModel({ args.illustId }) { illustId -> + private val pixivViewModel by pixivValueViewModel({ args.illustId }) { hint, illustId -> val resp = Client.appApi.getIllust(illustId) resp.illust?.let { illust -> ObjectPool.update(illust) @@ -72,6 +73,10 @@ class IllustFragment : ImgDisplayFragment(R.layout.fragment_fancy_illust), Galle override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding.toolbarLayout.naviMore.setOnClick { + pushFragment(R.id.navigation_related_illusts, RelatedIllustsFragmentArgs(args.illustId).toBundle()) + } + setUpFullScreen( viewModel, listOf( @@ -95,33 +100,13 @@ class IllustFragment : ImgDisplayFragment(R.layout.fragment_fancy_illust), Galle ) } } - + binding.bookmark.setOnClick { + onClickBookmarkIllust(it, args.illustId) + } if (illust.is_bookmarked == true) { binding.bookmark.setImageResource(R.drawable.icon_liked) - binding.bookmark.setOnClick { - launchSuspend(it) { - Client.appApi.removeBookmark(args.illustId) - ObjectPool.update( - illust.copy( - is_bookmarked = false, - total_bookmarks = illust.total_bookmarks?.minus(1) - ) - ) - } - } } else { binding.bookmark.setImageResource(R.drawable.icon_not_liked) - binding.bookmark.setOnClick { - launchSuspend(it) { - Client.appApi.postBookmark(args.illustId) - ObjectPool.update( - illust.copy( - is_bookmarked = true, - total_bookmarks = illust.total_bookmarks?.plus(1) - ) - ) - } - } } if (illust.page_count == 1) { @@ -131,7 +116,7 @@ class IllustFragment : ImgDisplayFragment(R.layout.fragment_fancy_illust), Galle } if (illust.caption?.isNotEmpty() == true) { binding.description.isVisible = true - binding.description.text = illust.caption + binding.description.text = HtmlCompat.fromHtml(illust.caption, HtmlCompat.FROM_HTML_MODE_COMPACT) } else { binding.description.isVisible = false } diff --git a/app/src/main/java/ceui/pixiv/widgets/MenuDialog.kt b/app/src/main/java/ceui/pixiv/widgets/MenuDialog.kt index 789cb8734..100ee3c4f 100644 --- a/app/src/main/java/ceui/pixiv/widgets/MenuDialog.kt +++ b/app/src/main/java/ceui/pixiv/widgets/MenuDialog.kt @@ -37,6 +37,7 @@ open class MenuDialog : PixivDialog(R.layout.dialog_menu), MenuActionReceiver { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.menuList.layoutManager = LinearLayoutManager(requireContext()) + binding.menuList.itemAnimator = null val adapter = CommonAdapter(viewLifecycleOwner) binding.menuList.adapter = adapter adapter.submitList(args.menuItems.map { menuItem -> diff --git a/app/src/main/java/ceui/pixiv/widgets/PendingTaskViewModel.kt b/app/src/main/java/ceui/pixiv/widgets/PendingTaskViewModel.kt deleted file mode 100644 index 3e93bb7d0..000000000 --- a/app/src/main/java/ceui/pixiv/widgets/PendingTaskViewModel.kt +++ /dev/null @@ -1,57 +0,0 @@ -package ceui.pixiv.widgets - -import android.os.Parcelable -import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModel -import ceui.lisa.utils.Common -import kotlinx.coroutines.CompletableDeferred -import java.io.Serializable -import java.util.concurrent.ConcurrentHashMap - -data class FragmentResultByFragment( - val result: ResultT, - val fragment: FragmentT, -) - -class FragmentResultStore : ViewModel() { - - private val _taskMap = hashMapOf>>() - private val _pendingResultMap = hashMapOf() - private val _resultListener = hashMapOf() - - fun registerListener(requestId: String, lifecycle: Lifecycle) { - _resultListener[requestId] = lifecycle - Common.showLog("dsaasdw registerListener ${requestId}") - } - - fun unRegisterListener(requestId: String) { - _resultListener[requestId] = null - Common.showLog("dsaasdw unRegisterListener ${requestId}") - } - - fun getExistingLifecycle(requestId: String): Lifecycle? { - return _resultListener[requestId] - } - - fun putTask(requestId: String, task: CompletableDeferred>) { - _taskMap[requestId] = task - } - - fun getTypedTask(requestId: String): CompletableDeferred>? { - return _taskMap[requestId] - } - - - fun putResult(fragmentUniqueId: String, result: Any) { - _pendingResultMap[fragmentUniqueId] = result - } - - fun getTypedResult(fragmentUniqueId: String): Any? { - return _pendingResultMap[fragmentUniqueId] - } - - fun removeResult(fragmentUniqueId: String) { - _pendingResultMap.remove(fragmentUniqueId) - } -} \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/widgets/PixivBottomSheet.kt b/app/src/main/java/ceui/pixiv/widgets/PixivBottomSheet.kt index 37705fb94..a39ea8575 100644 --- a/app/src/main/java/ceui/pixiv/widgets/PixivBottomSheet.kt +++ b/app/src/main/java/ceui/pixiv/widgets/PixivBottomSheet.kt @@ -8,18 +8,14 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import ceui.lisa.R import ceui.lisa.utils.Common -import ceui.loxia.FRAGMENT_RESULT_REQUEST_ID -import ceui.pixiv.ui.common.FragmentResultRequestIdOwner import ceui.pixiv.ui.common.NavFragmentViewModel import ceui.refactor.screenHeight import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlin.math.roundToInt -open class PixivBottomSheet(layoutId: Int) : BottomSheetDialogFragment(layoutId), - FragmentResultRequestIdOwner { +open class PixivBottomSheet(layoutId: Int) : BottomSheetDialogFragment(layoutId) { - private val fragmentResultStore by activityViewModels() private val fragmentViewModel: NavFragmentViewModel by viewModels() protected val viewModel by activityViewModels() @@ -46,25 +42,4 @@ open class PixivBottomSheet(layoutId: Int) : BottomSheetDialogFragment(layoutId) fragmentViewModel.viewCreatedTime.value = System.currentTimeMillis() } - - fun setFragmentResult(result: T) { - resultRequestId?.let { requestId -> - val existingLifecycle = fragmentResultStore.getExistingLifecycle(requestId) - if (existingLifecycle != null && existingLifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { - - Common.showLog("dsaasdw STARTED getTypedResult ${result}") -// fragmentResultStore.getTypedTask(requestId)?.let { task -> -// task.complete(FragmentResultByFragment(result, fragment)) -// fragmentResultStore.removeResult(requestId) -// } - } else { - fragmentResultStore.putResult(requestId, result) - } - } - } - - override val resultRequestId: String? - get() = arguments?.getString(FRAGMENT_RESULT_REQUEST_ID) - override val fragmentUniqueId: String - get() = fragmentViewModel.fragmentUniqueId } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/widgets/PixivDialog.kt b/app/src/main/java/ceui/pixiv/widgets/PixivDialog.kt index ab599def5..11336cca6 100644 --- a/app/src/main/java/ceui/pixiv/widgets/PixivDialog.kt +++ b/app/src/main/java/ceui/pixiv/widgets/PixivDialog.kt @@ -9,8 +9,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import ceui.lisa.R import ceui.loxia.Event -import ceui.loxia.FRAGMENT_RESULT_REQUEST_ID -import ceui.pixiv.ui.common.FragmentResultRequestIdOwner import ceui.pixiv.ui.common.NavFragmentViewModel import kotlinx.coroutines.CompletableDeferred @@ -25,11 +23,10 @@ class DialogViewModel : ViewModel() { val triggerUsersYoriEvent = MutableLiveData>() } -open class PixivDialog(layoutId: Int) : DialogFragment(layoutId), FragmentResultRequestIdOwner { +open class PixivDialog(layoutId: Int) : DialogFragment(layoutId) { protected val viewModel by activityViewModels() private val fragmentViewModel: NavFragmentViewModel by viewModels() - private val fragmentResultStore by activityViewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -63,15 +60,4 @@ open class PixivDialog(layoutId: Int) : DialogFragment(layoutId), FragmentResult fragmentViewModel.viewCreatedTime.value = System.currentTimeMillis() } - - fun setFragmentResult(result: T) { - resultRequestId?.let { requestId -> - fragmentResultStore.putResult(requestId, result) - } - } - - override val resultRequestId: String? - get() = arguments?.getString(FRAGMENT_RESULT_REQUEST_ID) - override val fragmentUniqueId: String - get() = fragmentViewModel.fragmentUniqueId } \ No newline at end of file diff --git a/app/src/main/java/ceui/pixiv/widgets/TagsFlowView.kt b/app/src/main/java/ceui/pixiv/widgets/TagsFlowView.kt index 4916dabf7..87000b55a 100644 --- a/app/src/main/java/ceui/pixiv/widgets/TagsFlowView.kt +++ b/app/src/main/java/ceui/pixiv/widgets/TagsFlowView.kt @@ -18,9 +18,11 @@ import androidx.databinding.InverseBindingAdapter import androidx.databinding.InverseBindingListener import androidx.fragment.app.Fragment import ceui.lisa.R +import ceui.lisa.activities.Shaft import ceui.lisa.databinding.SmallTagCellBinding import ceui.lisa.databinding.TagCellBinding import ceui.lisa.models.TagsBean +import ceui.lisa.utils.Dev import ceui.loxia.ObjectType import ceui.loxia.Tag import ceui.loxia.WebTag @@ -30,6 +32,7 @@ import ceui.loxia.hideKeyboard import ceui.pixiv.utils.ColorRandom import ceui.pixiv.utils.ShapedDrawables import ceui.pixiv.utils.getIntColor +import com.blankj.utilcode.util.ColorUtils import com.google.android.flexbox.FlexboxLayout class TagsFlowView(context: Context, attrs: AttributeSet?, defStyle: Int) @@ -151,8 +154,8 @@ class TagsFlowView(context: Context, attrs: AttributeSet?, defStyle: Int) val height = child.layoutParams.height.toFloat() - val borderColor = getIntColor(ColorRandom.randomColorFromTag(tag)) - val backgroundColorString = ColorRandom.randomColorFromTag(tag) + val borderColor = getIntColor(Shaft.getThemeColor()) + val backgroundColorString = Shaft.getThemeColor() val backgroundWithAlpha = backgroundColorString.replace("#", "#33") val backgroundColor = getIntColor(backgroundWithAlpha) @@ -166,9 +169,9 @@ class TagsFlowView(context: Context, attrs: AttributeSet?, defStyle: Int) child.background = selector val textView = child.findViewById(R.id.hashtag_name) - textView.text = (tag.translated_name?.takeIf { it.isNotEmpty() } ?: tag.name) + " " + backgroundColorString + textView.text = (tag.translated_name?.takeIf { it.isNotEmpty() } ?: tag.name) - val normalTextColor = getIntColor(ColorRandom.randomColorFromTag(tag)) + val normalTextColor = getIntColor(Shaft.getThemeColor()) val selectedTextColor = Color.WHITE val colorSelector = ColorStateList( diff --git a/app/src/main/res/drawable/progress_bar_custom.xml b/app/src/main/res/drawable/progress_bar_custom.xml new file mode 100644 index 000000000..fc9821f80 --- /dev/null +++ b/app/src/main/res/drawable/progress_bar_custom.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/quened_task_bg.xml b/app/src/main/res/drawable/quened_task_bg.xml new file mode 100644 index 000000000..bae6ea2e9 --- /dev/null +++ b/app/src/main/res/drawable/quened_task_bg.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/quened_task_bg_not_selected.xml b/app/src/main/res/drawable/quened_task_bg_not_selected.xml new file mode 100644 index 000000000..7193ffe2f --- /dev/null +++ b/app/src/main/res/drawable/quened_task_bg_not_selected.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/quened_task_bg_selected.xml b/app/src/main/res/drawable/quened_task_bg_selected.xml new file mode 100644 index 000000000..cb00856e3 --- /dev/null +++ b/app/src/main/res/drawable/quened_task_bg_selected.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/status_running_bg.xml b/app/src/main/res/drawable/status_running_bg.xml new file mode 100644 index 000000000..0243729c1 --- /dev/null +++ b/app/src/main/res/drawable/status_running_bg.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/status_running_bg_not_selected.xml b/app/src/main/res/drawable/status_running_bg_not_selected.xml new file mode 100644 index 000000000..27eba64d5 --- /dev/null +++ b/app/src/main/res/drawable/status_running_bg_not_selected.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/status_running_bg_selected.xml b/app/src/main/res/drawable/status_running_bg_selected.xml new file mode 100644 index 000000000..8b87519dc --- /dev/null +++ b/app/src/main/res/drawable/status_running_bg_selected.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/cell_illust_card.xml b/app/src/main/res/layout/cell_illust_card.xml index 65e74ca99..59ea92700 100644 --- a/app/src/main/res/layout/cell_illust_card.xml +++ b/app/src/main/res/layout/cell_illust_card.xml @@ -1,23 +1,53 @@ - + - + + + + + + android:layout_height="wrap_content"> + + + + - + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/cell_novel_text.xml b/app/src/main/res/layout/cell_novel_text.xml index 0c9830812..406911640 100644 --- a/app/src/main/res/layout/cell_novel_text.xml +++ b/app/src/main/res/layout/cell_novel_text.xml @@ -9,7 +9,6 @@ android:id="@+id/novel_text" android:layout_width="match_parent" android:textSize="17sp" - android:layout_marginHorizontal="@dimen/sixteen_dp" android:lineSpacingExtra="@dimen/six_dp" android:textIsSelectable="true" android:textColor="@color/white_to_gray" diff --git a/app/src/main/res/layout/cell_queued_task.xml b/app/src/main/res/layout/cell_queued_task.xml new file mode 100644 index 000000000..e0d4647fa --- /dev/null +++ b/app/src/main/res/layout/cell_queued_task.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/cell_task_preview.xml b/app/src/main/res/layout/cell_task_preview.xml index a5b8ba38e..d8640df39 100644 --- a/app/src/main/res/layout/cell_task_preview.xml +++ b/app/src/main/res/layout/cell_task_preview.xml @@ -14,8 +14,7 @@ + android:background="@drawable/cell_background"> - + + + + @@ -15,30 +18,27 @@ + android:background="@drawable/cell_background"> - @@ -47,22 +47,34 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="12dp" + android:layout_marginTop="2dp" android:textColor="@color/text20" android:textSize="13sp" - android:layout_marginTop="2dp" app:layout_constraintStart_toEndOf="@+id/user_icon" app:layout_constraintTop_toBottomOf="@+id/user_name" /> + + app:rRadius="6dp" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_alert.xml b/app/src/main/res/layout/dialog_alert.xml index 335bc1618..669967b31 100644 --- a/app/src/main/res/layout/dialog_alert.xml +++ b/app/src/main/res/layout/dialog_alert.xml @@ -1,85 +1,92 @@ - + android:layout_gravity="center"> - + android:background="@drawable/bg_pixiv_dialog" + android:orientation="vertical"> - + - + - + - + - + android:layout_marginHorizontal="20dp" + android:layout_marginBottom="20dp"> - + android:layout_marginEnd="5dp" + android:layout_weight="1" + android:background="@drawable/bg_white_button"> - + - + - + android:layout_marginStart="5dp" + android:layout_weight="1" + android:background="@drawable/bg_green_button"> - + - + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_menu.xml b/app/src/main/res/layout/dialog_menu.xml index 822c3cb07..5968d28f6 100644 --- a/app/src/main/res/layout/dialog_menu.xml +++ b/app/src/main/res/layout/dialog_menu.xml @@ -1,15 +1,22 @@ - + android:layout_gravity="center"> - + android:layout_height="wrap_content" + android:layout_marginHorizontal="20dp" + android:background="@drawable/bg_pixiv_dialog" + android:orientation="vertical"> - \ No newline at end of file + + + + + diff --git a/app/src/main/res/layout/fragment_fancy_illust.xml b/app/src/main/res/layout/fragment_fancy_illust.xml index 314283f26..5a6b474d7 100644 --- a/app/src/main/res/layout/fragment_fancy_illust.xml +++ b/app/src/main/res/layout/fragment_fancy_illust.xml @@ -179,7 +179,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="60dp" - android:layout_marginEnd="20dp" + android:layout_marginEnd="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content"> diff --git a/app/src/main/res/layout/fragment_home_viewpager.xml b/app/src/main/res/layout/fragment_home_viewpager.xml index fa4f0f6ee..2356eac0a 100644 --- a/app/src/main/res/layout/fragment_home_viewpager.xml +++ b/app/src/main/res/layout/fragment_home_viewpager.xml @@ -39,7 +39,7 @@ android:id="@+id/home_header_content" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingVertical="12dp" + android:paddingVertical="8dp" android:paddingStart="18dp" android:paddingEnd="18dp" android:paddingBottom="10dp"> diff --git a/app/src/main/res/layout/fragment_pixiv_list.xml b/app/src/main/res/layout/fragment_pixiv_list.xml index b994eb5d9..d2ae9bfea 100644 --- a/app/src/main/res/layout/fragment_pixiv_list.xml +++ b/app/src/main/res/layout/fragment_pixiv_list.xml @@ -114,6 +114,7 @@ android:id="@+id/empty_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginHorizontal="50dp" android:layout_gravity="center" android:gravity="center" android:orientation="vertical" @@ -138,6 +139,7 @@ android:id="@+id/error_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginHorizontal="50dp" android:layout_gravity="center" android:gravity="center" android:orientation="vertical" diff --git a/app/src/main/res/layout/fragment_rank_preview.xml b/app/src/main/res/layout/fragment_rank_preview.xml new file mode 100644 index 000000000..9e792ea09 --- /dev/null +++ b/app/src/main/res/layout/fragment_rank_preview.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rank_viewpager.xml b/app/src/main/res/layout/fragment_rank_viewpager.xml new file mode 100644 index 000000000..992bb4fb6 --- /dev/null +++ b/app/src/main/res/layout/fragment_rank_viewpager.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 46489bb44..11c13a222 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -16,6 +16,11 @@ android:name="ceui.pixiv.ui.article.ArticlesFragment" tools:layout="@layout/fragment_pixiv_list" /> + + + + + + 42dp -48dp + 44dp \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9f0f89742..af3c31997 100644 --- a/build.gradle +++ b/build.gradle @@ -14,13 +14,13 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + classpath 'com.android.tools.build:gradle:8.7.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25" classpath 'com.google.gms:google-services:4.4.2' classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.2' classpath "org.jmailen.gradle:kotlinter-gradle:3.7.0" - def nav_version = '2.7.7' + def nav_version = '2.8.4' classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files