Merge branch 'devel' into feature/mal
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@
|
||||
/.idea/deploymentTargetDropDown.xml
|
||||
/.idea/androidTestResultsUserPreferences.xml
|
||||
/.idea/render.experimental.xml
|
||||
/.idea/inspectionProfiles/
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
|
||||
17
.idea/inspectionProfiles/Project_Default.xml
generated
17
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,17 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="BooleanLiteralArgument" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="Destructure" enabled="true" level="INFO" enabled_by_default="true" />
|
||||
<inspection_tool class="FillClass" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||
<option name="withoutDefaultValues" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="KeySetIterationMayUseEntrySet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="KotlinFunctionArgumentsHelper" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||
<option name="withoutDefaultValues" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TrailingComma" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 512
|
||||
versionName '4.3.1'
|
||||
versionCode 513
|
||||
versionName '4.3.2'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -86,7 +86,7 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:e5a6b82853') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:7f630184c0') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.ActionBarContextView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.view.ViewCompat
|
||||
@@ -51,12 +52,9 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
EntryPointAccessors.fromApplication(this, BaseActivityEntryPoint::class.java).inject(this)
|
||||
val isAmoled = settings.isAmoledTheme
|
||||
val isDynamic = settings.isDynamicTheme
|
||||
when {
|
||||
isAmoled && isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet_Amoled)
|
||||
isAmoled -> setTheme(R.style.Theme_Kotatsu_Amoled)
|
||||
isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet)
|
||||
setTheme(settings.colorScheme.styleResId)
|
||||
if (settings.isAmoledTheme) {
|
||||
setTheme(R.style.ThemeOverlay_Kotatsu_Amoled)
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
@@ -89,9 +87,8 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
} else super.onOptionsItemSelected(item)
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove
|
||||
// ActivityCompat.recreate(this)
|
||||
TODO("Test error")
|
||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
ActivityCompat.recreate(this)
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
|
||||
@@ -9,13 +9,13 @@ import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
|
||||
import org.koitharu.kotatsu.utils.ext.displayCompat
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
|
||||
@@ -27,6 +27,9 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
protected val behavior: BottomSheetBehavior<*>?
|
||||
get() = (dialog as? BottomSheetDialog)?.behavior
|
||||
|
||||
val isExpanded: Boolean
|
||||
get() = behavior?.state == BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
final override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
||||
@@ -4,10 +4,12 @@ import android.animation.LayoutTransition
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
@@ -15,16 +17,16 @@ import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.view.*
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import java.util.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.databinding.LayoutSheetHeaderBinding
|
||||
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeDrawable
|
||||
import org.koitharu.kotatsu.utils.ext.parents
|
||||
import java.util.*
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
private const val THROTTLE_DELAY = 200L
|
||||
|
||||
@@ -53,6 +55,9 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
val toolbar: MaterialToolbar
|
||||
get() = binding.toolbar
|
||||
|
||||
val menu: Menu
|
||||
get() = binding.toolbar.menu
|
||||
|
||||
var title: CharSequence?
|
||||
get() = binding.toolbar.title
|
||||
set(value) {
|
||||
@@ -140,6 +145,10 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
binding.toolbar.invalidateMenu()
|
||||
}
|
||||
|
||||
fun inflateMenu(@MenuRes resId: Int) {
|
||||
binding.toolbar.inflateMenu(resId)
|
||||
}
|
||||
|
||||
fun setNavigationOnClickListener(onClickListener: OnClickListener) {
|
||||
binding.toolbar.setNavigationOnClickListener(onClickListener)
|
||||
}
|
||||
@@ -258,6 +267,7 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
}
|
||||
lp
|
||||
}
|
||||
|
||||
else -> Toolbar.LayoutParams(params)
|
||||
}
|
||||
}
|
||||
@@ -282,7 +292,7 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
suppressLayoutCompat(false)
|
||||
}
|
||||
|
||||
private inner class Callback : BottomSheetBehavior.BottomSheetCallback(), View.OnClickListener {
|
||||
private inner class Callback : BottomSheetBehavior.BottomSheetCallback(), OnClickListener {
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
onBottomSheetStateChanged(newState)
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.view.children
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
|
||||
class CheckableButtonGroup @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = materialR.attr.materialButtonToggleGroupStyle,
|
||||
) : LinearLayout(context, attrs, defStyleAttr, materialR.style.Widget_MaterialComponents_MaterialButtonToggleGroup),
|
||||
View.OnClickListener {
|
||||
|
||||
private val originalCornerData = ArrayList<CornerData>()
|
||||
|
||||
var onCheckedChangeListener: OnCheckedChangeListener? = null
|
||||
|
||||
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
|
||||
if (child is MaterialButton) {
|
||||
setupButton(child)
|
||||
}
|
||||
super.addView(child, index, params)
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
updateChildShapes()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
setCheckedId(v.id)
|
||||
}
|
||||
|
||||
fun setCheckedId(@IdRes viewRes: Int) {
|
||||
children.forEach {
|
||||
(it as? MaterialButton)?.isChecked = it.id == viewRes
|
||||
}
|
||||
onCheckedChangeListener?.onCheckedChanged(this, viewRes)
|
||||
}
|
||||
|
||||
private fun updateChildShapes() {
|
||||
val childCount = childCount
|
||||
val firstVisibleChildIndex = 0
|
||||
val lastVisibleChildIndex = childCount - 1
|
||||
for (i in 0 until childCount) {
|
||||
val button: MaterialButton = getChildAt(i) as? MaterialButton ?: continue
|
||||
if (button.visibility == GONE) {
|
||||
continue
|
||||
}
|
||||
val builder = button.shapeAppearanceModel.toBuilder()
|
||||
val newCornerData: CornerData? =
|
||||
getNewCornerData(i, firstVisibleChildIndex, lastVisibleChildIndex)
|
||||
updateBuilderWithCornerData(builder, newCornerData)
|
||||
button.shapeAppearanceModel = builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButton(button: MaterialButton) {
|
||||
button.setOnClickListener(this)
|
||||
button.isElegantTextHeight = false
|
||||
// Saves original corner data
|
||||
val shapeAppearanceModel: ShapeAppearanceModel = button.shapeAppearanceModel
|
||||
originalCornerData.add(
|
||||
CornerData(
|
||||
shapeAppearanceModel.topLeftCornerSize,
|
||||
shapeAppearanceModel.bottomLeftCornerSize,
|
||||
shapeAppearanceModel.topRightCornerSize,
|
||||
shapeAppearanceModel.bottomRightCornerSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNewCornerData(
|
||||
index: Int,
|
||||
firstVisibleChildIndex: Int,
|
||||
lastVisibleChildIndex: Int,
|
||||
): CornerData? {
|
||||
val cornerData: CornerData = originalCornerData.get(index)
|
||||
|
||||
// If only one (visible) child exists, use its original corners
|
||||
if (firstVisibleChildIndex == lastVisibleChildIndex) {
|
||||
return cornerData
|
||||
}
|
||||
val isHorizontal = orientation == HORIZONTAL
|
||||
if (index == firstVisibleChildIndex) {
|
||||
return if (isHorizontal) cornerData.start(this) else cornerData.top()
|
||||
}
|
||||
return if (index == lastVisibleChildIndex) {
|
||||
if (isHorizontal) cornerData.end(this) else cornerData.bottom()
|
||||
} else null
|
||||
}
|
||||
|
||||
private fun updateBuilderWithCornerData(
|
||||
shapeAppearanceModelBuilder: ShapeAppearanceModel.Builder,
|
||||
cornerData: CornerData?,
|
||||
) {
|
||||
if (cornerData == null) {
|
||||
shapeAppearanceModelBuilder.setAllCornerSizes(0f)
|
||||
return
|
||||
}
|
||||
shapeAppearanceModelBuilder
|
||||
.setTopLeftCornerSize(cornerData.topLeft)
|
||||
.setBottomLeftCornerSize(cornerData.bottomLeft)
|
||||
.setTopRightCornerSize(cornerData.topRight)
|
||||
.setBottomRightCornerSize(cornerData.bottomRight)
|
||||
}
|
||||
|
||||
fun interface OnCheckedChangeListener {
|
||||
fun onCheckedChanged(group: CheckableButtonGroup, checkedId: Int)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.google.android.material.shape.AbsoluteCornerSize
|
||||
import com.google.android.material.shape.CornerSize
|
||||
|
||||
class CornerData(
|
||||
var topLeft: CornerSize,
|
||||
var bottomLeft: CornerSize,
|
||||
var topRight: CornerSize,
|
||||
var bottomRight: CornerSize,
|
||||
) {
|
||||
|
||||
fun start(view: View): CornerData {
|
||||
return if (isLayoutRtl(view)) right() else left()
|
||||
}
|
||||
|
||||
fun end(view: View): CornerData {
|
||||
return if (isLayoutRtl(view)) left() else right()
|
||||
}
|
||||
|
||||
fun left(): CornerData {
|
||||
return CornerData(topLeft, bottomLeft, noCorner, noCorner)
|
||||
}
|
||||
|
||||
fun right(): CornerData {
|
||||
return CornerData(noCorner, noCorner, topRight, bottomRight)
|
||||
}
|
||||
|
||||
fun top(): CornerData {
|
||||
return CornerData(topLeft, noCorner, topRight, noCorner)
|
||||
}
|
||||
|
||||
fun bottom(): CornerData {
|
||||
return CornerData(noCorner, bottomLeft, noCorner, bottomRight)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val noCorner: CornerSize = AbsoluteCornerSize(0f)
|
||||
|
||||
fun isLayoutRtl(view: View): Boolean {
|
||||
return ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.graphics.withClip
|
||||
import com.google.android.material.drawable.DrawableUtils
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class ShapeView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
private val corners = FloatArray(8)
|
||||
private val outlinePath = Path()
|
||||
private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
|
||||
init {
|
||||
context.withStyledAttributes(attrs, R.styleable.ShapeView, defStyleAttr) {
|
||||
val cornerSize = getDimension(R.styleable.ShapeView_cornerSize, 0f)
|
||||
corners[0] = getDimension(R.styleable.ShapeView_cornerSizeTopLeft, cornerSize)
|
||||
corners[1] = corners[0]
|
||||
corners[2] = getDimension(R.styleable.ShapeView_cornerSizeTopRight, cornerSize)
|
||||
corners[3] = corners[2]
|
||||
corners[4] = getDimension(R.styleable.ShapeView_cornerSizeBottomRight, cornerSize)
|
||||
corners[5] = corners[4]
|
||||
corners[6] = getDimension(R.styleable.ShapeView_cornerSizeBottomLeft, cornerSize)
|
||||
corners[7] = corners[6]
|
||||
strokePaint.color = getColor(R.styleable.ShapeView_strokeColor, Color.TRANSPARENT)
|
||||
strokePaint.strokeWidth = getDimension(R.styleable.ShapeView_strokeWidth, 0f)
|
||||
strokePaint.style = Paint.Style.STROKE
|
||||
}
|
||||
outlineProvider = OutlineProvider()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
if (w != oldw || h != oldh) {
|
||||
rebuildPath()
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
canvas.withClip(outlinePath) {
|
||||
super.draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
if (strokePaint.strokeWidth > 0f) {
|
||||
canvas.drawPath(outlinePath, strokePaint)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rebuildPath() {
|
||||
outlinePath.reset()
|
||||
val w = width
|
||||
val h = height
|
||||
if (w > 0 && h > 0) {
|
||||
outlinePath.addRoundRect(0f, 0f, w.toFloat(), h.toFloat(), corners, Path.Direction.CW)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class OutlineProvider : ViewOutlineProvider() {
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun getOutline(view: View?, outline: Outline) {
|
||||
val corner = corners[0]
|
||||
var isRoundRect = true
|
||||
for (item in corners) {
|
||||
if (item != corner) {
|
||||
isRoundRect = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isRoundRect) {
|
||||
outline.setRoundRect(0, 0, width, height, corner)
|
||||
} else {
|
||||
DrawableUtils.setOutlineToPath(outline, outlinePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import androidx.collection.arraySetOf
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
@@ -70,8 +69,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
val theme: Int
|
||||
get() = prefs.getString(KEY_THEME, null)?.toIntOrNull() ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
|
||||
val isDynamicTheme: Boolean
|
||||
get() = DynamicColors.isDynamicColorAvailable() && prefs.getBoolean(KEY_DYNAMIC_THEME, false)
|
||||
val colorScheme: ColorScheme
|
||||
get() = prefs.getEnumValue(KEY_COLOR_THEME, ColorScheme.default)
|
||||
|
||||
val isAmoledTheme: Boolean
|
||||
get() = prefs.getBoolean(KEY_THEME_AMOLED, false)
|
||||
@@ -312,7 +311,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
|
||||
const val KEY_LIST_MODE = "list_mode_2"
|
||||
const val KEY_THEME = "theme"
|
||||
const val KEY_DYNAMIC_THEME = "dynamic_theme"
|
||||
const val KEY_COLOR_THEME = "color_theme"
|
||||
const val KEY_THEME_AMOLED = "amoled_theme"
|
||||
const val KEY_DATE_FORMAT = "date_format"
|
||||
const val KEY_SOURCES_ORDER = "sources_order_2"
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.koitharu.kotatsu.core.prefs
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.StyleRes
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
enum class ColorScheme(
|
||||
@StyleRes val styleResId: Int,
|
||||
@StringRes val titleResId: Int,
|
||||
) {
|
||||
|
||||
DEFAULT(R.style.Theme_Kotatsu, R.string.system_default),
|
||||
MONET(R.style.Theme_Kotatsu_Monet, R.string.theme_name_dynamic),
|
||||
MINT(R.style.Theme_Kotatsu_Mint, R.string.theme_name_mint),
|
||||
OCTOBER(R.style.Theme_Kotatsu_October, R.string.theme_name_october),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
val default: ColorScheme
|
||||
get() = if (DynamicColors.isDynamicColorAvailable()) {
|
||||
MONET
|
||||
} else {
|
||||
DEFAULT
|
||||
}
|
||||
|
||||
fun getAvailableList(): List<ColorScheme> {
|
||||
val list = enumValues<ColorScheme>().toMutableList()
|
||||
if (!DynamicColors.isDynamicColorAvailable()) {
|
||||
list.remove(MONET)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun safeValueOf(name: String): ColorScheme? {
|
||||
return enumValues<ColorScheme>().find { it.name == name }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class DetailsMenuProvider(
|
||||
menu.findItem(R.id.action_delete).isVisible = manga?.source == MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_browser).isVisible = manga?.source != MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(activity)
|
||||
menu.findItem(R.id.action_shiki_track).isVisible = viewModel.isScrobblingAvailable
|
||||
menu.findItem(R.id.action_scrobbling).isVisible = viewModel.isScrobblingAvailable
|
||||
menu.findItem(R.id.action_favourite).setIcon(
|
||||
if (viewModel.favouriteCategories.value == true) R.drawable.ic_heart else R.drawable.ic_heart_outline,
|
||||
)
|
||||
@@ -60,11 +60,13 @@ class DetailsMenuProvider(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R.id.action_favourite -> {
|
||||
viewModel.manga.value?.let {
|
||||
FavouriteCategoriesBottomSheet.show(activity.supportFragmentManager, it)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.action_delete -> {
|
||||
val title = viewModel.manga.value?.title.orEmpty()
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
@@ -76,6 +78,7 @@ class DetailsMenuProvider(
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
R.id.action_save -> {
|
||||
viewModel.manga.value?.let {
|
||||
val chaptersCount = it.chapters?.size ?: 0
|
||||
@@ -87,21 +90,25 @@ class DetailsMenuProvider(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R.id.action_browser -> {
|
||||
viewModel.manga.value?.let {
|
||||
activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.title))
|
||||
}
|
||||
}
|
||||
|
||||
R.id.action_related -> {
|
||||
viewModel.manga.value?.let {
|
||||
activity.startActivity(MultiSearchActivity.newIntent(activity, it.title))
|
||||
}
|
||||
}
|
||||
R.id.action_shiki_track -> {
|
||||
|
||||
R.id.action_scrobbling -> {
|
||||
viewModel.manga.value?.let {
|
||||
ScrobblingSelectorBottomSheet.show(activity.supportFragmentManager, it)
|
||||
ScrobblingSelectorBottomSheet.show(activity.supportFragmentManager, it, null)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.action_shortcut -> {
|
||||
viewModel.manga.value?.let {
|
||||
activity.lifecycleScope.launch {
|
||||
@@ -112,6 +119,7 @@ class DetailsMenuProvider(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -256,29 +256,24 @@ class DetailsViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateScrobbling(rating: Float, status: ScrobblingStatus?) {
|
||||
for (info in scrobblingInfo.value ?: return) {
|
||||
val scrobbler = scrobblers.first { it.scrobblerService == info.scrobbler }
|
||||
if (!scrobbler.isAvailable) continue
|
||||
launchJob(Dispatchers.Default) {
|
||||
scrobbler.updateScrobblingInfo(
|
||||
mangaId = delegate.mangaId,
|
||||
rating = rating,
|
||||
status = status,
|
||||
comment = null,
|
||||
)
|
||||
}
|
||||
fun updateScrobbling(index: Int, rating: Float, status: ScrobblingStatus?) {
|
||||
val scrobbler = getScrobbler(index) ?: return
|
||||
launchJob(Dispatchers.Default) {
|
||||
scrobbler.updateScrobblingInfo(
|
||||
mangaId = delegate.mangaId,
|
||||
rating = rating,
|
||||
status = status,
|
||||
comment = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterScrobbling() {
|
||||
for (scrobbler in scrobblers) {
|
||||
if (!scrobbler.isAvailable) continue
|
||||
launchJob(Dispatchers.Default) {
|
||||
scrobbler.unregisterScrobbling(
|
||||
mangaId = delegate.mangaId,
|
||||
)
|
||||
}
|
||||
fun unregisterScrobbling(index: Int) {
|
||||
val scrobbler = getScrobbler(index) ?: return
|
||||
launchJob(Dispatchers.Default) {
|
||||
scrobbler.unregisterScrobbling(
|
||||
mangaId = delegate.mangaId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +310,19 @@ class DetailsViewModel @AssistedInject constructor(
|
||||
return spannable.trim()
|
||||
}
|
||||
|
||||
private fun getScrobbler(index: Int): Scrobbler? {
|
||||
val info = scrobblingInfo.value?.getOrNull(index)
|
||||
val scrobbler = if (info != null) {
|
||||
scrobblers.find { it.scrobblerService == info.scrobbler && it.isAvailable }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (scrobbler == null) {
|
||||
errorEvent.call(IllegalStateException("Scrobbler [$index] is not available"))
|
||||
}
|
||||
return scrobbler
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@ import androidx.core.net.toUri
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.databinding.SheetScrobblingBinding
|
||||
@@ -26,7 +24,12 @@ import org.koitharu.kotatsu.image.ui.ImageActivity
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.scrobbling.ui.selector.ScrobblingSelectorBottomSheet
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ScrobblingInfoBottomSheet :
|
||||
@@ -41,6 +44,7 @@ class ScrobblingInfoBottomSheet :
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private var menu: PopupMenu? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -78,6 +82,7 @@ class ScrobblingInfoBottomSheet :
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
viewModel.updateScrobbling(
|
||||
index = scrobblerIndex,
|
||||
rating = binding.ratingBar.rating / binding.ratingBar.numStars,
|
||||
status = enumValues<ScrobblingStatus>().getOrNull(position),
|
||||
)
|
||||
@@ -88,6 +93,7 @@ class ScrobblingInfoBottomSheet :
|
||||
override fun onRatingChanged(ratingBar: RatingBar, rating: Float, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
viewModel.updateScrobbling(
|
||||
index = scrobblerIndex,
|
||||
rating = rating / ratingBar.numStars,
|
||||
status = enumValues<ScrobblingStatus>().getOrNull(binding.spinnerStatus.selectedItemPosition),
|
||||
)
|
||||
@@ -115,15 +121,15 @@ class ScrobblingInfoBottomSheet :
|
||||
binding.ratingBar.rating = scrobbling.rating * binding.ratingBar.numStars
|
||||
binding.textViewDescription.text = scrobbling.description
|
||||
binding.spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1)
|
||||
ImageRequest.Builder(context ?: return)
|
||||
.target(binding.imageViewCover)
|
||||
.data(scrobbling.coverUrl)
|
||||
.crossfade(context)
|
||||
.lifecycle(viewLifecycleOwner)
|
||||
.placeholder(R.drawable.ic_placeholder)
|
||||
.fallback(R.drawable.ic_placeholder)
|
||||
.error(R.drawable.ic_error_placeholder)
|
||||
.enqueueWith(coil)
|
||||
binding.imageViewLogo.contentDescription = getString(scrobbling.scrobbler.titleResId)
|
||||
binding.imageViewLogo.setImageResource(scrobbling.scrobbler.iconResId)
|
||||
binding.imageViewCover.newImageRequest(scrobbling.coverUrl)?.apply {
|
||||
lifecycle(viewLifecycleOwner)
|
||||
placeholder(R.drawable.ic_placeholder)
|
||||
fallback(R.drawable.ic_placeholder)
|
||||
error(R.drawable.ic_error_placeholder)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
@@ -135,13 +141,16 @@ class ScrobblingInfoBottomSheet :
|
||||
Intent.createChooser(intent, getString(R.string.open_in_browser)),
|
||||
)
|
||||
}
|
||||
|
||||
R.id.action_unregister -> {
|
||||
viewModel.unregisterScrobbling()
|
||||
viewModel.unregisterScrobbling(scrobblerIndex)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
R.id.action_edit -> {
|
||||
val manga = viewModel.manga.value ?: return false
|
||||
ScrobblingSelectorBottomSheet.show(parentFragmentManager, manga)
|
||||
val scrobblerService = viewModel.scrobblingInfo.value?.getOrNull(scrobblerIndex)?.scrobbler
|
||||
ScrobblingSelectorBottomSheet.show(parentFragmentManager, manga, scrobblerService)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.scrobbling.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import org.jsoup.internal.StringUtil.StringJoiner
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser
|
||||
|
||||
@@ -39,12 +40,12 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
|
||||
remove(KEY_USER)
|
||||
return@edit
|
||||
}
|
||||
val str = buildString {
|
||||
appendLine(value.id)
|
||||
appendLine(value.nickname)
|
||||
appendLine(value.avatar)
|
||||
appendLine(value.service.name)
|
||||
}
|
||||
val str = StringJoiner("\n")
|
||||
.add(value.id)
|
||||
.add(value.nickname)
|
||||
.add(value.avatar)
|
||||
.add(value.service.name)
|
||||
.complete()
|
||||
putString(KEY_USER, str)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,18 @@ package org.koitharu.kotatsu.scrobbling.ui.selector
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
@@ -20,14 +22,18 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.ui.selector.adapter.ScrobblerMangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.scrobbling.ui.selector.adapter.ScrobblerSelectorAdapter
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.firstVisibleItemPosition
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.requireParcelable
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ScrobblingSelectorBottomSheet :
|
||||
@@ -38,7 +44,8 @@ class ScrobblingSelectorBottomSheet :
|
||||
MenuItem.OnActionExpandListener,
|
||||
SearchView.OnQueryTextListener,
|
||||
DialogInterface.OnKeyListener,
|
||||
AdapterView.OnItemSelectedListener {
|
||||
TabLayout.OnTabSelectedListener,
|
||||
ListStateHolderListener {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ScrobblingSelectorViewModel.Factory
|
||||
@@ -64,7 +71,7 @@ class ScrobblingSelectorBottomSheet :
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val listAdapter = ScrobblerSelectorAdapter(viewLifecycleOwner, coil, this)
|
||||
val listAdapter = ScrobblerSelectorAdapter(viewLifecycleOwner, coil, this, this)
|
||||
val decoration = ScrobblerMangaSelectionDecoration(view.context)
|
||||
with(binding.recyclerView) {
|
||||
adapter = listAdapter
|
||||
@@ -73,7 +80,7 @@ class ScrobblingSelectorBottomSheet :
|
||||
}
|
||||
binding.buttonDone.setOnClickListener(this)
|
||||
initOptionsMenu()
|
||||
initSpinner()
|
||||
initTabs()
|
||||
|
||||
viewModel.content.observe(viewLifecycleOwner) { listAdapter.items = it }
|
||||
viewModel.selectedItemId.observe(viewLifecycleOwner) {
|
||||
@@ -99,6 +106,12 @@ class ScrobblingSelectorBottomSheet :
|
||||
viewModel.selectedItemId.value = item.id
|
||||
}
|
||||
|
||||
override fun onRetryClick(error: Throwable) = Unit
|
||||
|
||||
override fun onEmptyActionClick() {
|
||||
openSearch()
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.loadList(append = true)
|
||||
}
|
||||
@@ -120,7 +133,7 @@ class ScrobblingSelectorBottomSheet :
|
||||
return false
|
||||
}
|
||||
viewModel.search(query)
|
||||
binding.headerBar.toolbar.menu.findItem(R.id.action_search)?.collapseActionView()
|
||||
binding.headerBar.menu.findItem(R.id.action_search)?.collapseActionView()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -128,7 +141,7 @@ class ScrobblingSelectorBottomSheet :
|
||||
|
||||
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
val menuItem = binding.headerBar.toolbar.menu.findItem(R.id.action_search) ?: return false
|
||||
val menuItem = binding.headerBar.menu.findItem(R.id.action_search) ?: return false
|
||||
if (menuItem.isActionViewExpanded) {
|
||||
if (event?.action == KeyEvent.ACTION_UP) {
|
||||
menuItem.collapseActionView()
|
||||
@@ -139,11 +152,23 @@ class ScrobblingSelectorBottomSheet :
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
viewModel.setScrobblerIndex(position)
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
viewModel.setScrobblerIndex(tab.position)
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||
if (!isExpanded) {
|
||||
setExpanded(isExpanded = true, isLocked = behavior?.isDraggable == false)
|
||||
}
|
||||
binding.recyclerView.firstVisibleItemPosition = 0
|
||||
}
|
||||
|
||||
private fun openSearch() {
|
||||
val menuItem = binding.headerBar.menu.findItem(R.id.action_search) ?: return
|
||||
menuItem.expandActionView()
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
Toast.makeText(requireContext(), e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
||||
@@ -153,8 +178,8 @@ class ScrobblingSelectorBottomSheet :
|
||||
}
|
||||
|
||||
private fun initOptionsMenu() {
|
||||
binding.headerBar.toolbar.inflateMenu(R.menu.opt_shiki_selector)
|
||||
val searchMenuItem = binding.headerBar.toolbar.menu.findItem(R.id.action_search)
|
||||
binding.headerBar.inflateMenu(R.menu.opt_shiki_selector)
|
||||
val searchMenuItem = binding.headerBar.menu.findItem(R.id.action_search)
|
||||
searchMenuItem.setOnActionExpandListener(this)
|
||||
val searchView = searchMenuItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(this)
|
||||
@@ -162,28 +187,41 @@ class ScrobblingSelectorBottomSheet :
|
||||
searchView.queryHint = searchMenuItem.title
|
||||
}
|
||||
|
||||
private fun initSpinner() {
|
||||
private fun initTabs() {
|
||||
val entries = viewModel.availableScrobblers
|
||||
val tabs = binding.tabs
|
||||
if (entries.size <= 1) {
|
||||
binding.spinnerScrobblers.isVisible = false
|
||||
tabs.isVisible = false
|
||||
return
|
||||
}
|
||||
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, entries)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
binding.spinnerScrobblers.adapter = adapter
|
||||
viewModel.selectedScrobblerIndex.observe(viewLifecycleOwner) {
|
||||
binding.spinnerScrobblers.setSelection(it)
|
||||
val selectedId = arguments?.getInt(ARG_SCROBBLER, -1) ?: -1
|
||||
tabs.removeAllTabs()
|
||||
tabs.clearOnTabSelectedListeners()
|
||||
tabs.addOnTabSelectedListener(this)
|
||||
for (entry in entries) {
|
||||
val tab = tabs.newTab()
|
||||
tab.tag = entry.scrobblerService
|
||||
tab.setIcon(entry.scrobblerService.iconResId)
|
||||
tab.setText(entry.scrobblerService.titleResId)
|
||||
tabs.addTab(tab)
|
||||
if (entry.scrobblerService.id == selectedId) {
|
||||
tab.select()
|
||||
}
|
||||
}
|
||||
binding.spinnerScrobblers.onItemSelectedListener = this
|
||||
tabs.isVisible = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "ScrobblingSelectorBottomSheet"
|
||||
private const val ARG_SCROBBLER = "scrobbler"
|
||||
|
||||
fun show(fm: FragmentManager, manga: Manga) =
|
||||
ScrobblingSelectorBottomSheet().withArgs(1) {
|
||||
fun show(fm: FragmentManager, manga: Manga, scrobblerService: ScrobblerService?) =
|
||||
ScrobblingSelectorBottomSheet().withArgs(2) {
|
||||
putParcelable(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = false))
|
||||
if (scrobblerService != null) {
|
||||
putInt(ARG_SCROBBLER, scrobblerService.id)
|
||||
}
|
||||
}.show(fm, TAG)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyHint
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
@@ -46,7 +48,7 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
||||
hasNextPage,
|
||||
) { list, isHasNextPage ->
|
||||
when {
|
||||
list.isEmpty() -> listOf()
|
||||
list.isEmpty() -> listOf(emptyResultsHint())
|
||||
isHasNextPage -> list + LoadingFooter
|
||||
else -> list
|
||||
}
|
||||
@@ -125,6 +127,13 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun emptyResultsHint() = EmptyHint(
|
||||
icon = R.drawable.ic_empty_history,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_search_holder_secondary,
|
||||
actionStringRes = R.string.search,
|
||||
)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
|
||||
@@ -4,23 +4,27 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD
|
||||
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
|
||||
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class ScrobblerSelectorAdapter(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
coil: ImageLoader,
|
||||
clickListener: OnListItemClickListener<ScrobblerManga>,
|
||||
stateHolderListener: ListStateHolderListener,
|
||||
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager.addDelegate(loadingStateAD())
|
||||
.addDelegate(scrobblerMangaAD(lifecycleOwner, coil, clickListener))
|
||||
.addDelegate(scrobblingMangaAD(lifecycleOwner, coil, clickListener))
|
||||
.addDelegate(loadingFooterAD())
|
||||
.addDelegate(emptyHintAD(stateHolderListener))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||
|
||||
fun scrobblerMangaAD(
|
||||
fun scrobblingMangaAD(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
coil: ImageLoader,
|
||||
clickListener: OnListItemClickListener<ScrobblerManga>,
|
||||
@@ -14,7 +14,6 @@ import androidx.core.view.postDelayed
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.TwoStatePreference
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
@@ -56,7 +55,6 @@ class AppearanceSettingsFragment :
|
||||
entryValues = ListMode.values().names()
|
||||
setDefaultValueCompat(ListMode.GRID.name)
|
||||
}
|
||||
findPreference<Preference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible = DynamicColors.isDynamicColorAvailable()
|
||||
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
||||
entryValues = resources.getStringArray(R.array.date_formats)
|
||||
val now = Date().time
|
||||
@@ -105,10 +103,7 @@ class AppearanceSettingsFragment :
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
}
|
||||
|
||||
AppSettings.KEY_DYNAMIC_THEME -> {
|
||||
postRestart()
|
||||
}
|
||||
|
||||
AppSettings.KEY_COLOR_THEME,
|
||||
AppSettings.KEY_THEME_AMOLED -> {
|
||||
postRestart()
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||
@@ -18,12 +20,14 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository
|
||||
import org.koitharu.kotatsu.scrobbling.data.ScrobblerRepository
|
||||
import org.koitharu.kotatsu.scrobbling.mal.data.MALRepository
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.FileSize
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -82,9 +86,9 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
bindShikimoriSummary()
|
||||
bindMALSummary()
|
||||
bindAniListSummary()
|
||||
bindScrobblerSummary(AppSettings.KEY_SHIKIMORI, shikimoriRepository)
|
||||
bindScrobblerSummary(AppSettings.KEY_ANILIST, aniListRepository)
|
||||
bindScrobblerSummary(AppSettings.KEY_MAL, malRepository)
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
@@ -125,7 +129,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
|
||||
AppSettings.KEY_SHIKIMORI -> {
|
||||
if (!shikimoriRepository.isAuthorized) {
|
||||
launchShikimoriAuth()
|
||||
launchScrobblerAuth(shikimoriRepository)
|
||||
true
|
||||
} else {
|
||||
super.onPreferenceTreeClick(preference)
|
||||
@@ -134,7 +138,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
|
||||
AppSettings.KEY_MAL -> {
|
||||
if (!malRepository.isAuthorized) {
|
||||
launchMALAuth()
|
||||
launchScrobblerAuth(malRepository)
|
||||
true
|
||||
} else {
|
||||
super.onPreferenceTreeClick(preference)
|
||||
@@ -143,7 +147,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
|
||||
AppSettings.KEY_ANILIST -> {
|
||||
if (!aniListRepository.isAuthorized) {
|
||||
launchAniListAuth()
|
||||
launchScrobblerAuth(aniListRepository)
|
||||
true
|
||||
} else {
|
||||
super.onPreferenceTreeClick(preference)
|
||||
@@ -213,54 +217,35 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun bindShikimoriSummary() {
|
||||
findPreference<Preference>(AppSettings.KEY_SHIKIMORI)?.summary = if (shikimoriRepository.isAuthorized) {
|
||||
getString(R.string.logged_in_as, shikimoriRepository.cachedUser?.nickname)
|
||||
private fun bindScrobblerSummary(key: String, repository: ScrobblerRepository) {
|
||||
val pref = findPreference<Preference>(key) ?: return
|
||||
if (!repository.isAuthorized) {
|
||||
pref.setSummary(R.string.disabled)
|
||||
return
|
||||
}
|
||||
val username = repository.cachedUser?.nickname
|
||||
if (username != null) {
|
||||
pref.summary = getString(R.string.logged_in_as, username)
|
||||
} else {
|
||||
getString(R.string.disabled)
|
||||
pref.setSummary(R.string.loading_)
|
||||
viewLifecycleScope.launch {
|
||||
pref.summary = withContext(Dispatchers.Default) {
|
||||
runCatching {
|
||||
val user = repository.loadUser()
|
||||
getString(R.string.logged_in_as, user.nickname)
|
||||
}.getOrElse {
|
||||
it.printStackTraceDebug()
|
||||
it.getDisplayMessage(resources)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindAniListSummary() {
|
||||
findPreference<Preference>(AppSettings.KEY_ANILIST)?.summary = if (aniListRepository.isAuthorized) {
|
||||
getString(R.string.logged_in_as, aniListRepository.cachedUser?.nickname)
|
||||
} else {
|
||||
getString(R.string.disabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchShikimoriAuth() {
|
||||
private fun launchScrobblerAuth(repository: ScrobblerRepository) {
|
||||
runCatching {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(shikimoriRepository.oauthUrl)
|
||||
startActivity(intent)
|
||||
}.onFailure {
|
||||
Snackbar.make(listView, it.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindMALSummary() {
|
||||
findPreference<Preference>(AppSettings.KEY_MAL)?.summary = if (malRepository.isAuthorized) {
|
||||
getString(R.string.logged_in_as, malRepository.cachedUser?.nickname)
|
||||
} else {
|
||||
getString(R.string.disabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchMALAuth() {
|
||||
runCatching {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(malRepository.oauthUrl)
|
||||
startActivity(intent)
|
||||
}.onFailure {
|
||||
Snackbar.make(listView, it.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchAniListAuth() {
|
||||
runCatching {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(aniListRepository.oauthUrl)
|
||||
intent.data = Uri.parse(repository.oauthUrl)
|
||||
startActivity(intent)
|
||||
}.onFailure {
|
||||
Snackbar.make(listView, it.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.koitharu.kotatsu.settings.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.ColorScheme
|
||||
import org.koitharu.kotatsu.databinding.ItemColorSchemeBinding
|
||||
|
||||
class ThemeChooserPreference @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = R.attr.themeChooserPreferenceStyle,
|
||||
defStyleRes: Int = R.style.Preference_ThemeChooser,
|
||||
) : Preference(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
private val entries = ColorScheme.getAvailableList()
|
||||
private var currentValue: ColorScheme = ColorScheme.default
|
||||
private val itemClickListener = View.OnClickListener {
|
||||
val tag = it.tag as? ColorScheme ?: return@OnClickListener
|
||||
setValueInternal(tag.name, true)
|
||||
}
|
||||
|
||||
var value: String
|
||||
get() = currentValue.name
|
||||
set(value) = setValueInternal(value, notifyChanged = true)
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
val layout = holder.findViewById(R.id.linear) as? LinearLayout ?: return
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
layout.suppressLayout(true)
|
||||
}
|
||||
layout.removeAllViews()
|
||||
for (theme in entries) {
|
||||
val context = ContextThemeWrapper(context, theme.styleResId)
|
||||
val item = ItemColorSchemeBinding.inflate(LayoutInflater.from(context), layout, false)
|
||||
item.card.isChecked = theme == currentValue
|
||||
item.textViewTitle.setText(theme.titleResId)
|
||||
item.root.tag = theme
|
||||
item.card.tag = theme
|
||||
item.imageViewCheck.isVisible = theme == currentValue
|
||||
item.root.setOnClickListener(itemClickListener)
|
||||
item.card.setOnClickListener(itemClickListener)
|
||||
layout.addView(item.root)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
layout.suppressLayout(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSetInitialValue(defaultValue: Any?) {
|
||||
value = getPersistedString(
|
||||
when (defaultValue) {
|
||||
is String -> ColorScheme.safeValueOf(defaultValue) ?: ColorScheme.default
|
||||
is ColorScheme -> defaultValue
|
||||
else -> ColorScheme.default
|
||||
}.name,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onGetDefaultValue(a: TypedArray, index: Int): Any {
|
||||
return a.getInt(index, 0)
|
||||
}
|
||||
|
||||
private fun setValueInternal(enumName: String, notifyChanged: Boolean) {
|
||||
val newValue = ColorScheme.safeValueOf(enumName) ?: return
|
||||
if (newValue != currentValue) {
|
||||
currentValue = newValue
|
||||
persistString(newValue.name)
|
||||
if (notifyChanged) {
|
||||
notifyChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
app/src/main/res/layout/item_color_scheme.xml
Normal file
96
app/src/main/res/layout/item_color_scheme.xml
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="6dp"
|
||||
tools:theme="@style/Theme.Kotatsu.Mint">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card"
|
||||
style="?materialCardViewFilledStyle"
|
||||
android:layout_width="@dimen/widget_cover_width"
|
||||
android:layout_height="@dimen/widget_cover_height"
|
||||
android:focusableInTouchMode="false">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="6dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Abc"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ShapeView
|
||||
android:id="@+id/shape_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="?colorSecondary"
|
||||
app:cornerSize="4dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/shape_2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintWidth_percent="0.4" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ShapeView
|
||||
android:id="@+id/shape_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="6dp"
|
||||
android:background="?colorSecondary"
|
||||
app:cornerSize="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.65"
|
||||
app:layout_constraintWidth_percent="0.7" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ShapeView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:background="?colorPrimary"
|
||||
app:cornerSize="6dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_check"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="6dp"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_mtrl_checked_circle"
|
||||
app:tint="?colorPrimary"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elegantTextHeight="false"
|
||||
android:ellipsize="end"
|
||||
android:paddingTop="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="@string/theme_name_mint" />
|
||||
</LinearLayout>
|
||||
76
app/src/main/res/layout/preference_theme.xml
Normal file
76
app/src/main/res/layout/preference_theme.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
tools:ignore="PrivateResource">
|
||||
|
||||
<include layout="@layout/image_frame" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="true"
|
||||
android:baselineAlignedChildIndex="0"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="marquee"
|
||||
android:labelFor="@id/seekbar"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
style="@style/PreferenceSummaryTextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:scrollIndicators="start|end"
|
||||
android:scrollbars="none"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/dragHandle"
|
||||
@@ -35,6 +36,17 @@
|
||||
tools:background="@sample/covers[9]"
|
||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_logo"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_margin="@dimen/card_indicator_offset"
|
||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover"
|
||||
app:tint="?colorControlLight"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/ic_shikimori" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
@@ -103,7 +115,6 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textIsSelectable="true"
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
|
||||
</org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_scrobblers"
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@android:layout/simple_spinner_item" />
|
||||
android:visibility="gone"
|
||||
app:tabGravity="start"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
@@ -36,7 +38,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:padding="@dimen/grid_spacing"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_manga_list" />
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_shiki_track"
|
||||
android:id="@+id/action_scrobbling"
|
||||
android:orderInCategory="50"
|
||||
android:title="@string/tracking"
|
||||
app:showAsAction="never" />
|
||||
|
||||
61
app/src/main/res/values-night-v23/color_themes.xml
Normal file
61
app/src/main/res/values-night-v23/color_themes.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Colored themes -->
|
||||
<style name="Theme.Kotatsu.Mint">
|
||||
<item name="colorPrimary">#4CDBCE</item>
|
||||
<item name="colorOnPrimary">#003733</item>
|
||||
<item name="colorPrimaryContainer">#00504A</item>
|
||||
<item name="colorOnPrimaryContainer">#6EF8EA</item>
|
||||
<item name="colorSecondary">#B1CCC8</item>
|
||||
<item name="colorOnSecondary">#1C3532</item>
|
||||
<item name="colorSecondaryContainer">#324B48</item>
|
||||
<item name="colorOnSecondaryContainer">#CCE8E4</item>
|
||||
<item name="colorTertiary">#AFC9E7</item>
|
||||
<item name="colorOnTertiary">#17324A</item>
|
||||
<item name="colorTertiaryContainer">#2F4961</item>
|
||||
<item name="colorOnTertiaryContainer">#CEE5FF</item>
|
||||
<item name="colorError">#FFB4AB</item>
|
||||
<item name="colorErrorContainer">#93000A</item>
|
||||
<item name="colorOnError">#690005</item>
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="android:colorBackground">#191C1C</item>
|
||||
<item name="colorOnBackground">#E0E3E1</item>
|
||||
<item name="colorSurface">#191C1C</item>
|
||||
<item name="colorOnSurface">#E0E3E1</item>
|
||||
<item name="colorSurfaceVariant">#3F4947</item>
|
||||
<item name="colorOnSurfaceVariant">#BEC9C6</item>
|
||||
<item name="colorOutline">#899391</item>
|
||||
<item name="colorOnSurfaceInverse">#191C1C</item>
|
||||
<item name="colorSurfaceInverse">#E0E3E1</item>
|
||||
<item name="colorPrimaryInverse">#006A63</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.October">
|
||||
<item name="colorPrimary">#FFB3AF</item>
|
||||
<item name="colorOnPrimary">#68000E</item>
|
||||
<item name="colorPrimaryContainer">#930018</item>
|
||||
<item name="colorOnPrimaryContainer">#FFDAD7</item>
|
||||
<item name="colorSecondary">#FFB783</item>
|
||||
<item name="colorOnSecondary">#4F2500</item>
|
||||
<item name="colorSecondaryContainer">#713700</item>
|
||||
<item name="colorOnSecondaryContainer">#FFDCC5</item>
|
||||
<item name="colorTertiary">#E2C28C</item>
|
||||
<item name="colorOnTertiary">#412D05</item>
|
||||
<item name="colorTertiaryContainer">#594319</item>
|
||||
<item name="colorOnTertiaryContainer">#FFDEA9</item>
|
||||
<item name="colorError">#FFB4AB</item>
|
||||
<item name="colorErrorContainer">#93000A</item>
|
||||
<item name="colorOnError">#690005</item>
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="android:colorBackground">#201A1A</item>
|
||||
<item name="colorOnBackground">#EDE0DE</item>
|
||||
<item name="colorSurface">#201A1A</item>
|
||||
<item name="colorOnSurface">#EDE0DE</item>
|
||||
<item name="colorSurfaceVariant">#534342</item>
|
||||
<item name="colorOnSurfaceVariant">#D8C1C0</item>
|
||||
<item name="colorOutline">#A08C8B</item>
|
||||
<item name="colorOnSurfaceInverse">#201A1A</item>
|
||||
<item name="colorSurfaceInverse">#EDE0DE</item>
|
||||
<item name="colorPrimaryInverse">#BA1928</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu" parent="ThemeOverlay.Material3.Dark" />
|
||||
|
||||
<style name="Theme.Kotatsu.Amoled">
|
||||
<style name="ThemeOverlay.Kotatsu.Amoled" parent="">
|
||||
<item name="colorSurface">@color/surface_amoled</item>
|
||||
<item name="android:colorBackground">@color/background_amoled</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Monet.Amoled">
|
||||
<item name="colorSurface">@color/surface_amoled</item>
|
||||
<item name="android:colorBackground">@color/background_amoled</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
4
app/src/main/res/values-v23/bools.xml
Normal file
4
app/src/main/res/values-v23/bools.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="is_color_themes_available">true</bool>
|
||||
</resources>
|
||||
61
app/src/main/res/values-v23/color_themes.xml
Normal file
61
app/src/main/res/values-v23/color_themes.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Colored themes -->
|
||||
<style name="Theme.Kotatsu.Mint">
|
||||
<item name="colorPrimary">#006A63</item>
|
||||
<item name="colorOnPrimary">#FFFFFF</item>
|
||||
<item name="colorPrimaryContainer">#6EF8EA</item>
|
||||
<item name="colorOnPrimaryContainer">#00201D</item>
|
||||
<item name="colorSecondary">#4A6360</item>
|
||||
<item name="colorOnSecondary">#FFFFFF</item>
|
||||
<item name="colorSecondaryContainer">#CCE8E4</item>
|
||||
<item name="colorOnSecondaryContainer">#051F1D</item>
|
||||
<item name="colorTertiary">#47617A</item>
|
||||
<item name="colorOnTertiary">#FFFFFF</item>
|
||||
<item name="colorTertiaryContainer">#CEE5FF</item>
|
||||
<item name="colorOnTertiaryContainer">#001D33</item>
|
||||
<item name="colorError">#BA1A1A</item>
|
||||
<item name="colorErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOnError">#FFFFFF</item>
|
||||
<item name="colorOnErrorContainer">#410002</item>
|
||||
<item name="android:colorBackground">#FAFDFB</item>
|
||||
<item name="colorOnBackground">#191C1C</item>
|
||||
<item name="colorSurface">#FAFDFB</item>
|
||||
<item name="colorOnSurface">#191C1C</item>
|
||||
<item name="colorSurfaceVariant">#DAE5E2</item>
|
||||
<item name="colorOnSurfaceVariant">#3F4947</item>
|
||||
<item name="colorOutline">#6F7977</item>
|
||||
<item name="colorOnSurfaceInverse">#EFF1F0</item>
|
||||
<item name="colorSurfaceInverse">#2D3130</item>
|
||||
<item name="colorPrimaryInverse">#4CDBCE</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.October">
|
||||
<item name="colorPrimary">#BA1928</item>
|
||||
<item name="colorOnPrimary">#FFFFFF</item>
|
||||
<item name="colorPrimaryContainer">#FFDAD7</item>
|
||||
<item name="colorOnPrimaryContainer">#410005</item>
|
||||
<item name="colorSecondary">#944B00</item>
|
||||
<item name="colorOnSecondary">#FFFFFF</item>
|
||||
<item name="colorSecondaryContainer">#FFDCC5</item>
|
||||
<item name="colorOnSecondaryContainer">#301400</item>
|
||||
<item name="colorTertiary">#735B2E</item>
|
||||
<item name="colorOnTertiary">#FFFFFF</item>
|
||||
<item name="colorTertiaryContainer">#FFDEA9</item>
|
||||
<item name="colorOnTertiaryContainer">#271900</item>
|
||||
<item name="colorError">#BA1A1A</item>
|
||||
<item name="colorErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOnError">#FFFFFF</item>
|
||||
<item name="colorOnErrorContainer">#410002</item>
|
||||
<item name="android:colorBackground">#FFFBFF</item>
|
||||
<item name="colorOnBackground">#201A1A</item>
|
||||
<item name="colorSurface">#FFFBFF</item>
|
||||
<item name="colorOnSurface">#201A1A</item>
|
||||
<item name="colorSurfaceVariant">#F4DDDB</item>
|
||||
<item name="colorOnSurfaceVariant">#534342</item>
|
||||
<item name="colorOutline">#857372</item>
|
||||
<item name="colorOnSurfaceInverse">#FBEEEC</item>
|
||||
<item name="colorSurfaceInverse">#362F2E</item>
|
||||
<item name="colorPrimaryInverse">#FFB3AF</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -4,6 +4,7 @@
|
||||
<attr name="sliderPreferenceStyle" />
|
||||
<attr name="multiAutoCompleteTextViewPreferenceStyle" />
|
||||
<attr name="autoCompleteTextViewPreferenceStyle" />
|
||||
<attr name="themeChooserPreferenceStyle" />
|
||||
<attr name="listItemTextViewStyle" />
|
||||
<attr name="fastScrollerStyle" />
|
||||
|
||||
@@ -75,4 +76,14 @@
|
||||
<attr name="fitStatusBar" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ShapeView">
|
||||
<attr name="strokeWidth" />
|
||||
<attr name="strokeColor" />
|
||||
<attr name="cornerSize" />
|
||||
<attr name="cornerSizeTopLeft" />
|
||||
<attr name="cornerSizeTopRight" />
|
||||
<attr name="cornerSizeBottomLeft" />
|
||||
<attr name="cornerSizeBottomRight" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
<bool name="light_status_bar">true</bool>
|
||||
<bool name="light_navigation_bar">false</bool>
|
||||
<bool name="com_samsung_android_icon_container_has_icon_container">true</bool>
|
||||
<bool name="is_color_themes_available">false</bool>
|
||||
</resources>
|
||||
|
||||
7
app/src/main/res/values/color_themes.xml
Normal file
7
app/src/main/res/values/color_themes.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Kotatsu.Mint" />
|
||||
|
||||
<style name="Theme.Kotatsu.October" />
|
||||
</resources>
|
||||
@@ -408,4 +408,8 @@
|
||||
<string name="enable_logging">Enable logging</string>
|
||||
<string name="enable_logging_summary">Record some actions for debug purposes</string>
|
||||
<string name="show_suspicious_content">Show suspicious content</string>
|
||||
<string name="theme_name_mint">Mint</string>
|
||||
<string name="theme_name_dynamic">Dynamic</string>
|
||||
<string name="color_theme">Color scheme</string>
|
||||
<string name="theme_name_october">October</string>
|
||||
</resources>
|
||||
|
||||
@@ -255,6 +255,11 @@
|
||||
<item name="android:widgetLayout">@layout/preference_widget_material_switch</item>
|
||||
</style>
|
||||
|
||||
<style name="Preference.ThemeChooser" parent="Preference.Material">
|
||||
<item name="android:layout">@layout/preference_theme</item>
|
||||
<item name="android:selectable">false</item>
|
||||
</style>
|
||||
|
||||
<!-- Progress drawable -->
|
||||
|
||||
<style name="ProgressDrawable">
|
||||
|
||||
@@ -87,12 +87,10 @@
|
||||
<!-- Monet theme only support S+ -->
|
||||
<style name="Theme.Kotatsu.Monet" />
|
||||
|
||||
<style name="Theme.Kotatsu.Amoled" />
|
||||
|
||||
<style name="Theme.Kotatsu.Monet.Amoled" />
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu" parent="ThemeOverlay.Material3.Light" />
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu.Amoled" parent="" />
|
||||
|
||||
<style name="Theme.Kotatsu.Dialog" parent="">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
|
||||
@@ -11,12 +11,10 @@
|
||||
android:title="@string/theme"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="dynamic_theme"
|
||||
android:summary="@string/dynamic_theme_summary"
|
||||
android:title="@string/dynamic_theme"
|
||||
app:isPreferenceVisible="false" />
|
||||
<org.koitharu.kotatsu.settings.utils.ThemeChooserPreference
|
||||
android:key="color_theme"
|
||||
android:title="@string/color_theme"
|
||||
app:isPreferenceVisible="@bool/is_color_themes_available" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
@@ -26,7 +24,8 @@
|
||||
|
||||
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
||||
android:key="app_locale"
|
||||
android:title="@string/language" />
|
||||
android:title="@string/language"
|
||||
app:allowDividerAbove="true" />
|
||||
|
||||
<ListPreference
|
||||
android:key="date_format"
|
||||
@@ -36,7 +35,6 @@
|
||||
android:entries="@array/list_modes"
|
||||
android:key="list_mode_2"
|
||||
android:title="@string/list_mode"
|
||||
app:allowDividerAbove="true"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||
|
||||
Reference in New Issue
Block a user