diff --git a/app/src/main/java/io/legato/kazusa/base/BaseService.kt b/app/src/main/java/io/legato/kazusa/base/BaseService.kt index af160fedd..411fd4eb3 100644 --- a/app/src/main/java/io/legato/kazusa/base/BaseService.kt +++ b/app/src/main/java/io/legato/kazusa/base/BaseService.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import io.legato.kazusa.R import io.legato.kazusa.help.LifecycleHelp +import io.legato.kazusa.help.config.AppConfig import io.legato.kazusa.help.coroutine.Coroutine import io.legato.kazusa.lib.permission.Permissions import io.legato.kazusa.lib.permission.PermissionsCompat @@ -37,7 +38,10 @@ abstract class BaseService : LifecycleService() { override fun onCreate() { super.onCreate() LifecycleHelp.onServiceCreate(this) - checkPermission() + if (!AppConfig.permissionChecked) { + AppConfig.permissionChecked = true + checkPermission() + } } @CallSuper diff --git a/app/src/main/java/io/legato/kazusa/constant/PreferKey.kt b/app/src/main/java/io/legato/kazusa/constant/PreferKey.kt index 2fed36a38..52fbbf037 100644 --- a/app/src/main/java/io/legato/kazusa/constant/PreferKey.kt +++ b/app/src/main/java/io/legato/kazusa/constant/PreferKey.kt @@ -205,4 +205,8 @@ object PreferKey { const val mangaClickActionBC = "mangaClickActionBottomCenter" const val mangaClickActionBR = "mangaClickActionBottomRight" + const val permissionChecked = "permissionChecked" + const val notificationsPost = "notificationsPost" + const val ignoreBatteryPermission = "ignoreBatteryPermission" + } diff --git a/app/src/main/java/io/legato/kazusa/help/config/AppConfig.kt b/app/src/main/java/io/legato/kazusa/help/config/AppConfig.kt index f27bb1072..e9baa00f5 100644 --- a/app/src/main/java/io/legato/kazusa/help/config/AppConfig.kt +++ b/app/src/main/java/io/legato/kazusa/help/config/AppConfig.kt @@ -181,6 +181,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { } } + var permissionChecked: Boolean + get() = appCtx.getPrefBoolean(PreferKey.permissionChecked, false) + set(value) { + appCtx.putPrefBoolean(PreferKey.permissionChecked, value) + } + val textSelectAble: Boolean get() = appCtx.getPrefBoolean(PreferKey.textSelectAble, true) diff --git a/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListAdapter.kt b/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListAdapter.kt index a91466cbd..67d4aaf18 100644 --- a/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListAdapter.kt +++ b/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListAdapter.kt @@ -133,6 +133,7 @@ class ChapterListAdapter(context: Context, val callback: Callback) : tvChapterItem.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainer)) } else { tvChapterName.setTextColor(context.themeColor(com.google.android.material.R.attr.colorOnSurface)) + tvChapterItem.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurface)) } tvChapterName.text = getDisplayTitle(item) if (item.isVolume) { @@ -188,14 +189,21 @@ class ChapterListAdapter(context: Context, val callback: Callback) : private fun upHasCache(binding: ItemChapterListBinding, isDur: Boolean, cached: Boolean) = binding.apply { - ivChecked.setImageResource(R.drawable.ic_outline_cloud_24) - ivChecked.visible(!cached) - if (isDur) { - ivChecked.setImageResource(R.drawable.ic_check) - ivChecked.visible() + when { + isDur -> ivChecked.gone() + cached -> { + ivChecked.setImageResource(R.drawable.ic_check) + ivChecked.visible() + } + + else -> { + ivChecked.setImageResource(R.drawable.ic_outline_cloud_24) + ivChecked.visible() + } } } + interface Callback { val scope: CoroutineScope val book: Book? diff --git a/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListFragment.kt b/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListFragment.kt index e6a14b2cf..d09394559 100644 --- a/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListFragment.kt +++ b/app/src/main/java/io/legato/kazusa/ui/book/toc/ChapterListFragment.kt @@ -1,12 +1,20 @@ package io.legato.kazusa.ui.book.toc +//import io.legado.app.lib.theme.bottomBackground +//import io.legado.app.lib.theme.getPrimaryTextColor import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.Intent +import android.graphics.Canvas import android.os.Bundle import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toDrawable import androidx.fragment.app.activityViewModels +import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView import io.legato.kazusa.R import io.legato.kazusa.base.VMBaseFragment import io.legato.kazusa.constant.EventBus @@ -17,12 +25,15 @@ import io.legato.kazusa.databinding.FragmentChapterListBinding import io.legato.kazusa.help.book.BookHelp import io.legato.kazusa.help.book.isLocal import io.legato.kazusa.help.book.simulatedTotalChapterNum -//import io.legado.app.lib.theme.bottomBackground -//import io.legado.app.lib.theme.getPrimaryTextColor +import io.legato.kazusa.model.CacheBook import io.legato.kazusa.ui.widget.recycler.UpLinearLayoutManager import io.legato.kazusa.ui.widget.recycler.VerticalDivider +import io.legato.kazusa.utils.VibrationUtils import io.legato.kazusa.utils.applyNavigationBarMargin +import io.legato.kazusa.utils.dpToPx import io.legato.kazusa.utils.observeEvent +import io.legato.kazusa.utils.themeColor +import io.legato.kazusa.utils.toastOnUi import io.legato.kazusa.utils.viewbindingdelegate.viewBinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.Default @@ -30,6 +41,7 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlin.math.min class ChapterListFragment : VMBaseFragment(R.layout.fragment_chapter_list), ChapterListAdapter.Callback, @@ -42,12 +54,6 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragment_chapt override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run { viewModel.chapterListCallBack = this@ChapterListFragment -// val bbg = bottomBackground -// val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg)) -// llChapterBaseInfo.setBackgroundColor(bbg) -// tvCurrentChapterInfo.setTextColor(btc) -// ivChapterTop.setColorFilter(btc, PorterDuff.Mode.SRC_IN) -// ivChapterBottom.setColorFilter(btc, PorterDuff.Mode.SRC_IN) initRecyclerView() initView() viewModel.bookData.observe(this@ChapterListFragment) { @@ -56,12 +62,99 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragment_chapt } private fun initRecyclerView() { + val swipeCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) { + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean = false + + override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float = 0.3f + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val position = viewHolder.bindingAdapterPosition + val chapter = adapter.getItem(position) ?: return + val book = book ?: return + + toastOnUi("开始下载: ${adapter.getItem(position)?.title}") + CacheBook.start(requireContext(), book, chapter.index, chapter.index) + VibrationUtils.vibrate(context!!, 100) + adapter.notifyItemChanged(position) + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && dX > 0) { + val itemView = viewHolder.itemView + + val interpolator = FastOutSlowInInterpolator() + val dampedDx = + interpolator.getInterpolation(min(1f, dX / itemView.width)) * itemView.width + + // 背景 + val background = + requireContext().themeColor(com.google.android.material.R.attr.colorSecondary) + .toDrawable() + background.setBounds( + itemView.left, + itemView.top, + itemView.left + dampedDx.toInt(), + itemView.bottom + ) + background.draw(c) + + // 图标 + val icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_download)!! + val iconMargin = (itemView.height - icon.intrinsicHeight) / 2 + val iconTop = itemView.top + iconMargin + val iconBottom = iconTop + icon.intrinsicHeight + val iconLeft = 12.dpToPx() + val iconRight = iconLeft + icon.intrinsicWidth + icon.setTint(requireContext().themeColor(com.google.android.material.R.attr.colorOnSecondary)) + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom) + icon.draw(c) + + super.onChildDraw( + c, + recyclerView, + viewHolder, + dampedDx, + dY, + actionState, + isCurrentlyActive + ) + } else { + super.onChildDraw( + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive + ) + } + } + } + + val itemTouchHelper = ItemTouchHelper(swipeCallback) + itemTouchHelper.attachToRecyclerView(binding.recyclerView) + binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.addItemDecoration(VerticalDivider(requireContext())) binding.recyclerView.adapter = adapter } private fun initView() = binding.run { + cdBase.applyNavigationBarMargin() btnChapterTop.setOnClickListener { mLayoutManager.scrollToPositionWithOffset(0, 0) } @@ -70,10 +163,9 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragment_chapt mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0) } } - tvCurrentChapterInfo.setOnClickListener { + btnLocate.setOnClickListener { mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0) } - binding.llChapterBaseInfo.applyNavigationBarMargin() } @SuppressLint("SetTextI18n") diff --git a/app/src/main/java/io/legato/kazusa/ui/book/toc/TocActivity.kt b/app/src/main/java/io/legato/kazusa/ui/book/toc/TocActivity.kt index 0f8fa5832..0a05ebe2b 100644 --- a/app/src/main/java/io/legato/kazusa/ui/book/toc/TocActivity.kt +++ b/app/src/main/java/io/legato/kazusa/ui/book/toc/TocActivity.kt @@ -56,12 +56,10 @@ class TocActivity : VMBaseActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - tabLayout = binding.titleBar.findViewById(R.id.tab_layout) - //tabLayout.isTabIndicatorFullWidth = false - //tabLayout.setSelectedTabIndicatorColor(accentColor) + setSupportActionBar(binding.topBar) + tabLayout = binding.tabLayout binding.viewPager.adapter = TabFragmentPageAdapter() tabLayout.setupWithViewPager(binding.viewPager) - tabLayout.tabGravity = TabLayout.GRAVITY_CENTER viewModel.bookData.observe(this) { menu?.setGroupVisible(R.id.menu_group_text, it.isLocalTxt) } diff --git a/app/src/main/java/io/legato/kazusa/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legato/kazusa/ui/config/OtherConfigFragment.kt index 4180f508b..331126a64 100644 --- a/app/src/main/java/io/legato/kazusa/ui/config/OtherConfigFragment.kt +++ b/app/src/main/java/io/legato/kazusa/ui/config/OtherConfigFragment.kt @@ -1,11 +1,16 @@ package io.legato.kazusa.ui.config +//import io.legado.app.lib.theme.primaryColor import android.annotation.SuppressLint import android.content.ComponentName +import android.content.Context import android.content.SharedPreferences import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle +import android.os.PowerManager import android.view.View +import androidx.core.content.ContextCompat import androidx.core.view.postDelayed import androidx.fragment.app.activityViewModels import androidx.preference.ListPreference @@ -19,8 +24,9 @@ import io.legato.kazusa.help.AppFreezeMonitor import io.legato.kazusa.help.config.AppConfig import io.legato.kazusa.help.config.LocalConfig import io.legato.kazusa.lib.dialogs.alert +import io.legato.kazusa.lib.permission.Permissions +import io.legato.kazusa.lib.permission.PermissionsCompat import io.legato.kazusa.lib.prefs.fragment.PreferenceFragment -//import io.legado.app.lib.theme.primaryColor import io.legato.kazusa.model.CheckSource import io.legato.kazusa.model.ImageProvider import io.legato.kazusa.receiver.SharedReceiverActivity @@ -70,6 +76,7 @@ class OtherConfigFragment : PreferenceFragment(), upPreferenceSummary(PreferKey.bitmapCacheSize, AppConfig.bitmapCacheSize.toString()) upPreferenceSummary(PreferKey.imageRetainNum, AppConfig.imageRetainNum.toString()) upPreferenceSummary(PreferKey.sourceEditMaxLine, AppConfig.sourceEditMaxLine.toString()) + updatePermissionSummary() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -156,6 +163,9 @@ class OtherConfigFragment : PreferenceFragment(), PreferKey.clearWebViewData -> clearWebViewData() "localPassword" -> alertLocalPassword() PreferKey.shrinkDatabase -> shrinkDatabase() + + PreferKey.notificationsPost -> checkPermission(1) + PreferKey.ignoreBatteryPermission -> checkPermission(2) } return super.onPreferenceTreeClick(preference) } @@ -228,6 +238,30 @@ class OtherConfigFragment : PreferenceFragment(), } } + private fun updatePermissionSummary() { + val notificationsGranted: Boolean = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + requireContext(), android.Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } else { + true + } + upPreferenceSummary( + PreferKey.notificationsPost, + if (notificationsGranted) "已获取" else "未获取" + ) + + val pm = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager + val batteryGranted: Boolean = + pm.isIgnoringBatteryOptimizations(requireContext().packageName) + + upPreferenceSummary( + PreferKey.ignoreBatteryPermission, + if (batteryGranted) "已获取" else "未获取" + ) + } + private fun upPreferenceSummary(preferenceKey: String, value: String?) { val preference = findPreference(preferenceKey) ?: return @@ -255,6 +289,21 @@ class OtherConfigFragment : PreferenceFragment(), } } + private fun checkPermission(int: Int) { + if (int == 1) { + PermissionsCompat.Builder() + .addPermissions(Permissions.POST_NOTIFICATIONS) + .rationale(R.string.notification_permission_rationale) + .request() + } + if (int == 2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PermissionsCompat.Builder() + .addPermissions(Permissions.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + .rationale(R.string.ignore_battery_permission_rationale) + .request() + } + } + @SuppressLint("InflateParams") private fun showUserAgentDialog() { alert(getString(R.string.user_agent)) { diff --git a/app/src/main/res/drawable/ic_locate.xml b/app/src/main/res/drawable/ic_locate.xml new file mode 100644 index 000000000..f67eb4fa6 --- /dev/null +++ b/app/src/main/res/drawable/ic_locate.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_chapter_list.xml b/app/src/main/res/layout/activity_chapter_list.xml index 20700734b..ce8620028 100644 --- a/app/src/main/res/layout/activity_chapter_list.xml +++ b/app/src/main/res/layout/activity_chapter_list.xml @@ -1,22 +1,44 @@ - + android:layout_height="match_parent"> - + android:fitsSystemWindows="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + + + + + + + android:layout_height="0dp" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/app_bar" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_chapter_list.xml b/app/src/main/res/layout/fragment_chapter_list.xml index 204c8055b..8fc39ab4f 100644 --- a/app/src/main/res/layout/fragment_chapter_list.xml +++ b/app/src/main/res/layout/fragment_chapter_list.xml @@ -11,48 +11,70 @@ android:layout_height="0dp" android:overScrollMode="never" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@+id/ll_chapter_base_info" /> + app:layout_constraintBottom_toBottomOf="parent" /> - - + android:gravity="center" + android:orientation="horizontal" + android:paddingVertical="8dp" + android:paddingStart="16dp" + android:paddingEnd="10dp"> - + - + - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_bookshelf_grid.xml b/app/src/main/res/layout/item_bookshelf_grid.xml index bc98966f5..f7beb0567 100644 --- a/app/src/main/res/layout/item_bookshelf_grid.xml +++ b/app/src/main/res/layout/item_bookshelf_grid.xml @@ -9,6 +9,7 @@ android:id="@+id/cv_content" android:layout_width="match_parent" android:layout_height="wrap_content" + app:cardBackgroundColor="@android:color/transparent" android:layout_marginBottom="4dp" android:clickable="true" android:focusable="true" diff --git a/app/src/main/res/layout/item_chapter_list.xml b/app/src/main/res/layout/item_chapter_list.xml index ec014e5a8..f15b921d8 100644 --- a/app/src/main/res/layout/item_chapter_list.xml +++ b/app/src/main/res/layout/item_chapter_list.xml @@ -18,7 +18,7 @@ android:src="@drawable/ic_lock_outline" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:ignore="RtlSymmetry" /> @@ -26,33 +26,25 @@ android:id="@+id/tv_chapter_name" android:layout_width="0dp" android:layout_height="wrap_content" - android:textSize="15sp" + android:textSize="14sp" android:textStyle="bold" android:maxLines="2" - app:layout_constraintBottom_toTopOf="@id/barrier" - app:layout_constraintLeft_toRightOf="@+id/iv_locked" - app:layout_constraintRight_toLeftOf="@+id/iv_checked" + app:layout_constraintEnd_toStartOf="@+id/iv_checked" + app:layout_constraintStart_toEndOf="@+id/iv_locked" app:layout_constraintTop_toTopOf="parent" /> - - + app:layout_constraintEnd_toStartOf="@+id/iv_checked" + app:layout_constraintStart_toEndOf="@+id/iv_locked" + app:layout_constraintTop_toBottomOf="@id/tv_word_count" /> \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index a23d6b0d3..535faf2f0 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1237,5 +1237,6 @@ 检查更新中... 没有内容哦 匿名统计 + 定位至当前 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a3bf1a110..f6caa910b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1241,5 +1241,6 @@ 没有内容哦 匿名统计 允许应用使用Google Firebase进行崩溃和性能统计 + 定位至当前 diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index 8463738be..c64675aea 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -66,6 +66,18 @@ app:iconSpaceReserved="false" app:layout="@layout/view_preference_category"> + + + +