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/deploymentTargetDropDown.xml
|
||||||
/.idea/androidTestResultsUserPreferences.xml
|
/.idea/androidTestResultsUserPreferences.xml
|
||||||
/.idea/render.experimental.xml
|
/.idea/render.experimental.xml
|
||||||
|
/.idea/inspectionProfiles/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/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'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 512
|
versionCode 513
|
||||||
versionName '4.3.1'
|
versionName '4.3.2'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:e5a6b82853') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:7f630184c0') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.appcompat.widget.ActionBarContextView
|
import androidx.appcompat.widget.ActionBarContextView
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@@ -51,12 +52,9 @@ abstract class BaseActivity<B : ViewBinding> :
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
EntryPointAccessors.fromApplication(this, BaseActivityEntryPoint::class.java).inject(this)
|
EntryPointAccessors.fromApplication(this, BaseActivityEntryPoint::class.java).inject(this)
|
||||||
val isAmoled = settings.isAmoledTheme
|
setTheme(settings.colorScheme.styleResId)
|
||||||
val isDynamic = settings.isDynamicTheme
|
if (settings.isAmoledTheme) {
|
||||||
when {
|
setTheme(R.style.ThemeOverlay_Kotatsu_Amoled)
|
||||||
isAmoled && isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet_Amoled)
|
|
||||||
isAmoled -> setTheme(R.style.Theme_Kotatsu_Amoled)
|
|
||||||
isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet)
|
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
@@ -89,9 +87,8 @@ abstract class BaseActivity<B : ViewBinding> :
|
|||||||
} else super.onOptionsItemSelected(item)
|
} else super.onOptionsItemSelected(item)
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove
|
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
// ActivityCompat.recreate(this)
|
ActivityCompat.recreate(this)
|
||||||
TODO("Test error")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event)
|
return super.onKeyDown(keyCode, event)
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import android.view.ViewGroup
|
|||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.viewbinding.ViewBinding
|
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.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
|
import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
|
||||||
import org.koitharu.kotatsu.utils.ext.displayCompat
|
import org.koitharu.kotatsu.utils.ext.displayCompat
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
@@ -27,6 +27,9 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
|||||||
protected val behavior: BottomSheetBehavior<*>?
|
protected val behavior: BottomSheetBehavior<*>?
|
||||||
get() = (dialog as? BottomSheetDialog)?.behavior
|
get() = (dialog as? BottomSheetDialog)?.behavior
|
||||||
|
|
||||||
|
val isExpanded: Boolean
|
||||||
|
get() = behavior?.state == BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
|
||||||
final override fun onCreateView(
|
final override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import android.animation.LayoutTransition
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.annotation.MenuRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
@@ -15,16 +17,16 @@ import androidx.core.content.withStyledAttributes
|
|||||||
import androidx.core.view.*
|
import androidx.core.view.*
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
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.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import java.util.*
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.databinding.LayoutSheetHeaderBinding
|
import org.koitharu.kotatsu.databinding.LayoutSheetHeaderBinding
|
||||||
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
|
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
|
||||||
import org.koitharu.kotatsu.utils.ext.getThemeDrawable
|
import org.koitharu.kotatsu.utils.ext.getThemeDrawable
|
||||||
import org.koitharu.kotatsu.utils.ext.parents
|
import org.koitharu.kotatsu.utils.ext.parents
|
||||||
|
import java.util.*
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
private const val THROTTLE_DELAY = 200L
|
private const val THROTTLE_DELAY = 200L
|
||||||
|
|
||||||
@@ -53,6 +55,9 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
|||||||
val toolbar: MaterialToolbar
|
val toolbar: MaterialToolbar
|
||||||
get() = binding.toolbar
|
get() = binding.toolbar
|
||||||
|
|
||||||
|
val menu: Menu
|
||||||
|
get() = binding.toolbar.menu
|
||||||
|
|
||||||
var title: CharSequence?
|
var title: CharSequence?
|
||||||
get() = binding.toolbar.title
|
get() = binding.toolbar.title
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -140,6 +145,10 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
|||||||
binding.toolbar.invalidateMenu()
|
binding.toolbar.invalidateMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun inflateMenu(@MenuRes resId: Int) {
|
||||||
|
binding.toolbar.inflateMenu(resId)
|
||||||
|
}
|
||||||
|
|
||||||
fun setNavigationOnClickListener(onClickListener: OnClickListener) {
|
fun setNavigationOnClickListener(onClickListener: OnClickListener) {
|
||||||
binding.toolbar.setNavigationOnClickListener(onClickListener)
|
binding.toolbar.setNavigationOnClickListener(onClickListener)
|
||||||
}
|
}
|
||||||
@@ -258,6 +267,7 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
lp
|
lp
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Toolbar.LayoutParams(params)
|
else -> Toolbar.LayoutParams(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +292,7 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
|||||||
suppressLayoutCompat(false)
|
suppressLayoutCompat(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class Callback : BottomSheetBehavior.BottomSheetCallback(), View.OnClickListener {
|
private inner class Callback : BottomSheetBehavior.BottomSheetCallback(), OnClickListener {
|
||||||
|
|
||||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
onBottomSheetStateChanged(newState)
|
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.content.edit
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.color.DynamicColors
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||||
@@ -70,8 +69,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
val theme: Int
|
val theme: Int
|
||||||
get() = prefs.getString(KEY_THEME, null)?.toIntOrNull() ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
get() = prefs.getString(KEY_THEME, null)?.toIntOrNull() ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
|
|
||||||
val isDynamicTheme: Boolean
|
val colorScheme: ColorScheme
|
||||||
get() = DynamicColors.isDynamicColorAvailable() && prefs.getBoolean(KEY_DYNAMIC_THEME, false)
|
get() = prefs.getEnumValue(KEY_COLOR_THEME, ColorScheme.default)
|
||||||
|
|
||||||
val isAmoledTheme: Boolean
|
val isAmoledTheme: Boolean
|
||||||
get() = prefs.getBoolean(KEY_THEME_AMOLED, false)
|
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_LIST_MODE = "list_mode_2"
|
||||||
const val KEY_THEME = "theme"
|
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_THEME_AMOLED = "amoled_theme"
|
||||||
const val KEY_DATE_FORMAT = "date_format"
|
const val KEY_DATE_FORMAT = "date_format"
|
||||||
const val KEY_SOURCES_ORDER = "sources_order_2"
|
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_delete).isVisible = manga?.source == MangaSource.LOCAL
|
||||||
menu.findItem(R.id.action_browser).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_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(
|
menu.findItem(R.id.action_favourite).setIcon(
|
||||||
if (viewModel.favouriteCategories.value == true) R.drawable.ic_heart else R.drawable.ic_heart_outline,
|
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 -> {
|
R.id.action_favourite -> {
|
||||||
viewModel.manga.value?.let {
|
viewModel.manga.value?.let {
|
||||||
FavouriteCategoriesBottomSheet.show(activity.supportFragmentManager, it)
|
FavouriteCategoriesBottomSheet.show(activity.supportFragmentManager, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_delete -> {
|
R.id.action_delete -> {
|
||||||
val title = viewModel.manga.value?.title.orEmpty()
|
val title = viewModel.manga.value?.title.orEmpty()
|
||||||
MaterialAlertDialogBuilder(activity)
|
MaterialAlertDialogBuilder(activity)
|
||||||
@@ -76,6 +78,7 @@ class DetailsMenuProvider(
|
|||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_save -> {
|
R.id.action_save -> {
|
||||||
viewModel.manga.value?.let {
|
viewModel.manga.value?.let {
|
||||||
val chaptersCount = it.chapters?.size ?: 0
|
val chaptersCount = it.chapters?.size ?: 0
|
||||||
@@ -87,21 +90,25 @@ class DetailsMenuProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_browser -> {
|
R.id.action_browser -> {
|
||||||
viewModel.manga.value?.let {
|
viewModel.manga.value?.let {
|
||||||
activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.title))
|
activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.title))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_related -> {
|
R.id.action_related -> {
|
||||||
viewModel.manga.value?.let {
|
viewModel.manga.value?.let {
|
||||||
activity.startActivity(MultiSearchActivity.newIntent(activity, it.title))
|
activity.startActivity(MultiSearchActivity.newIntent(activity, it.title))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.action_shiki_track -> {
|
|
||||||
|
R.id.action_scrobbling -> {
|
||||||
viewModel.manga.value?.let {
|
viewModel.manga.value?.let {
|
||||||
ScrobblingSelectorBottomSheet.show(activity.supportFragmentManager, it)
|
ScrobblingSelectorBottomSheet.show(activity.supportFragmentManager, it, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_shortcut -> {
|
R.id.action_shortcut -> {
|
||||||
viewModel.manga.value?.let {
|
viewModel.manga.value?.let {
|
||||||
activity.lifecycleScope.launch {
|
activity.lifecycleScope.launch {
|
||||||
@@ -112,6 +119,7 @@ class DetailsMenuProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -256,29 +256,24 @@ class DetailsViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateScrobbling(rating: Float, status: ScrobblingStatus?) {
|
fun updateScrobbling(index: Int, rating: Float, status: ScrobblingStatus?) {
|
||||||
for (info in scrobblingInfo.value ?: return) {
|
val scrobbler = getScrobbler(index) ?: return
|
||||||
val scrobbler = scrobblers.first { it.scrobblerService == info.scrobbler }
|
launchJob(Dispatchers.Default) {
|
||||||
if (!scrobbler.isAvailable) continue
|
scrobbler.updateScrobblingInfo(
|
||||||
launchJob(Dispatchers.Default) {
|
mangaId = delegate.mangaId,
|
||||||
scrobbler.updateScrobblingInfo(
|
rating = rating,
|
||||||
mangaId = delegate.mangaId,
|
status = status,
|
||||||
rating = rating,
|
comment = null,
|
||||||
status = status,
|
)
|
||||||
comment = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterScrobbling() {
|
fun unregisterScrobbling(index: Int) {
|
||||||
for (scrobbler in scrobblers) {
|
val scrobbler = getScrobbler(index) ?: return
|
||||||
if (!scrobbler.isAvailable) continue
|
launchJob(Dispatchers.Default) {
|
||||||
launchJob(Dispatchers.Default) {
|
scrobbler.unregisterScrobbling(
|
||||||
scrobbler.unregisterScrobbling(
|
mangaId = delegate.mangaId,
|
||||||
mangaId = delegate.mangaId,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,6 +310,19 @@ class DetailsViewModel @AssistedInject constructor(
|
|||||||
return spannable.trim()
|
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
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ import androidx.core.net.toUri
|
|||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.request.ImageRequest
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||||
import org.koitharu.kotatsu.databinding.SheetScrobblingBinding
|
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.ScrobblingInfo
|
||||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
|
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
|
||||||
import org.koitharu.kotatsu.scrobbling.ui.selector.ScrobblingSelectorBottomSheet
|
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
|
@AndroidEntryPoint
|
||||||
class ScrobblingInfoBottomSheet :
|
class ScrobblingInfoBottomSheet :
|
||||||
@@ -41,6 +44,7 @@ class ScrobblingInfoBottomSheet :
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var coil: ImageLoader
|
lateinit var coil: ImageLoader
|
||||||
|
|
||||||
private var menu: PopupMenu? = null
|
private var menu: PopupMenu? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -78,6 +82,7 @@ class ScrobblingInfoBottomSheet :
|
|||||||
|
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
viewModel.updateScrobbling(
|
viewModel.updateScrobbling(
|
||||||
|
index = scrobblerIndex,
|
||||||
rating = binding.ratingBar.rating / binding.ratingBar.numStars,
|
rating = binding.ratingBar.rating / binding.ratingBar.numStars,
|
||||||
status = enumValues<ScrobblingStatus>().getOrNull(position),
|
status = enumValues<ScrobblingStatus>().getOrNull(position),
|
||||||
)
|
)
|
||||||
@@ -88,6 +93,7 @@ class ScrobblingInfoBottomSheet :
|
|||||||
override fun onRatingChanged(ratingBar: RatingBar, rating: Float, fromUser: Boolean) {
|
override fun onRatingChanged(ratingBar: RatingBar, rating: Float, fromUser: Boolean) {
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
viewModel.updateScrobbling(
|
viewModel.updateScrobbling(
|
||||||
|
index = scrobblerIndex,
|
||||||
rating = rating / ratingBar.numStars,
|
rating = rating / ratingBar.numStars,
|
||||||
status = enumValues<ScrobblingStatus>().getOrNull(binding.spinnerStatus.selectedItemPosition),
|
status = enumValues<ScrobblingStatus>().getOrNull(binding.spinnerStatus.selectedItemPosition),
|
||||||
)
|
)
|
||||||
@@ -115,15 +121,15 @@ class ScrobblingInfoBottomSheet :
|
|||||||
binding.ratingBar.rating = scrobbling.rating * binding.ratingBar.numStars
|
binding.ratingBar.rating = scrobbling.rating * binding.ratingBar.numStars
|
||||||
binding.textViewDescription.text = scrobbling.description
|
binding.textViewDescription.text = scrobbling.description
|
||||||
binding.spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1)
|
binding.spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1)
|
||||||
ImageRequest.Builder(context ?: return)
|
binding.imageViewLogo.contentDescription = getString(scrobbling.scrobbler.titleResId)
|
||||||
.target(binding.imageViewCover)
|
binding.imageViewLogo.setImageResource(scrobbling.scrobbler.iconResId)
|
||||||
.data(scrobbling.coverUrl)
|
binding.imageViewCover.newImageRequest(scrobbling.coverUrl)?.apply {
|
||||||
.crossfade(context)
|
lifecycle(viewLifecycleOwner)
|
||||||
.lifecycle(viewLifecycleOwner)
|
placeholder(R.drawable.ic_placeholder)
|
||||||
.placeholder(R.drawable.ic_placeholder)
|
fallback(R.drawable.ic_placeholder)
|
||||||
.fallback(R.drawable.ic_placeholder)
|
error(R.drawable.ic_error_placeholder)
|
||||||
.error(R.drawable.ic_error_placeholder)
|
enqueueWith(coil)
|
||||||
.enqueueWith(coil)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||||
@@ -135,13 +141,16 @@ class ScrobblingInfoBottomSheet :
|
|||||||
Intent.createChooser(intent, getString(R.string.open_in_browser)),
|
Intent.createChooser(intent, getString(R.string.open_in_browser)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_unregister -> {
|
R.id.action_unregister -> {
|
||||||
viewModel.unregisterScrobbling()
|
viewModel.unregisterScrobbling(scrobblerIndex)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_edit -> {
|
R.id.action_edit -> {
|
||||||
val manga = viewModel.manga.value ?: return false
|
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()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.scrobbling.data
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
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.ScrobblerService
|
||||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser
|
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser
|
||||||
|
|
||||||
@@ -39,12 +40,12 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
|
|||||||
remove(KEY_USER)
|
remove(KEY_USER)
|
||||||
return@edit
|
return@edit
|
||||||
}
|
}
|
||||||
val str = buildString {
|
val str = StringJoiner("\n")
|
||||||
appendLine(value.id)
|
.add(value.id)
|
||||||
appendLine(value.nickname)
|
.add(value.nickname)
|
||||||
appendLine(value.avatar)
|
.add(value.avatar)
|
||||||
appendLine(value.service.name)
|
.add(value.service.name)
|
||||||
}
|
.complete()
|
||||||
putString(KEY_USER, str)
|
putString(KEY_USER, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ package org.koitharu.kotatsu.scrobbling.ui.selector
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.KeyEvent
|
||||||
import android.widget.AdapterView
|
import android.view.LayoutInflater
|
||||||
import android.widget.ArrayAdapter
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
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.base.ui.list.PaginationScrollListener
|
||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
|
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.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
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.ScrobblerMangaSelectionDecoration
|
||||||
import org.koitharu.kotatsu.scrobbling.ui.selector.adapter.ScrobblerSelectorAdapter
|
import org.koitharu.kotatsu.scrobbling.ui.selector.adapter.ScrobblerSelectorAdapter
|
||||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
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.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.utils.ext.requireParcelable
|
import org.koitharu.kotatsu.utils.ext.requireParcelable
|
||||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ScrobblingSelectorBottomSheet :
|
class ScrobblingSelectorBottomSheet :
|
||||||
@@ -38,7 +44,8 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
MenuItem.OnActionExpandListener,
|
MenuItem.OnActionExpandListener,
|
||||||
SearchView.OnQueryTextListener,
|
SearchView.OnQueryTextListener,
|
||||||
DialogInterface.OnKeyListener,
|
DialogInterface.OnKeyListener,
|
||||||
AdapterView.OnItemSelectedListener {
|
TabLayout.OnTabSelectedListener,
|
||||||
|
ListStateHolderListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ScrobblingSelectorViewModel.Factory
|
lateinit var viewModelFactory: ScrobblingSelectorViewModel.Factory
|
||||||
@@ -64,7 +71,7 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val listAdapter = ScrobblerSelectorAdapter(viewLifecycleOwner, coil, this)
|
val listAdapter = ScrobblerSelectorAdapter(viewLifecycleOwner, coil, this, this)
|
||||||
val decoration = ScrobblerMangaSelectionDecoration(view.context)
|
val decoration = ScrobblerMangaSelectionDecoration(view.context)
|
||||||
with(binding.recyclerView) {
|
with(binding.recyclerView) {
|
||||||
adapter = listAdapter
|
adapter = listAdapter
|
||||||
@@ -73,7 +80,7 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
}
|
}
|
||||||
binding.buttonDone.setOnClickListener(this)
|
binding.buttonDone.setOnClickListener(this)
|
||||||
initOptionsMenu()
|
initOptionsMenu()
|
||||||
initSpinner()
|
initTabs()
|
||||||
|
|
||||||
viewModel.content.observe(viewLifecycleOwner) { listAdapter.items = it }
|
viewModel.content.observe(viewLifecycleOwner) { listAdapter.items = it }
|
||||||
viewModel.selectedItemId.observe(viewLifecycleOwner) {
|
viewModel.selectedItemId.observe(viewLifecycleOwner) {
|
||||||
@@ -99,6 +106,12 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
viewModel.selectedItemId.value = item.id
|
viewModel.selectedItemId.value = item.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRetryClick(error: Throwable) = Unit
|
||||||
|
|
||||||
|
override fun onEmptyActionClick() {
|
||||||
|
openSearch()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onScrolledToEnd() {
|
override fun onScrolledToEnd() {
|
||||||
viewModel.loadList(append = true)
|
viewModel.loadList(append = true)
|
||||||
}
|
}
|
||||||
@@ -120,7 +133,7 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
viewModel.search(query)
|
viewModel.search(query)
|
||||||
binding.headerBar.toolbar.menu.findItem(R.id.action_search)?.collapseActionView()
|
binding.headerBar.menu.findItem(R.id.action_search)?.collapseActionView()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +141,7 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
|
|
||||||
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
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 (menuItem.isActionViewExpanded) {
|
||||||
if (event?.action == KeyEvent.ACTION_UP) {
|
if (event?.action == KeyEvent.ACTION_UP) {
|
||||||
menuItem.collapseActionView()
|
menuItem.collapseActionView()
|
||||||
@@ -139,11 +152,23 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||||
viewModel.setScrobblerIndex(position)
|
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) {
|
private fun onError(e: Throwable) {
|
||||||
Toast.makeText(requireContext(), e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
Toast.makeText(requireContext(), e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
||||||
@@ -153,8 +178,8 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initOptionsMenu() {
|
private fun initOptionsMenu() {
|
||||||
binding.headerBar.toolbar.inflateMenu(R.menu.opt_shiki_selector)
|
binding.headerBar.inflateMenu(R.menu.opt_shiki_selector)
|
||||||
val searchMenuItem = binding.headerBar.toolbar.menu.findItem(R.id.action_search)
|
val searchMenuItem = binding.headerBar.menu.findItem(R.id.action_search)
|
||||||
searchMenuItem.setOnActionExpandListener(this)
|
searchMenuItem.setOnActionExpandListener(this)
|
||||||
val searchView = searchMenuItem.actionView as SearchView
|
val searchView = searchMenuItem.actionView as SearchView
|
||||||
searchView.setOnQueryTextListener(this)
|
searchView.setOnQueryTextListener(this)
|
||||||
@@ -162,28 +187,41 @@ class ScrobblingSelectorBottomSheet :
|
|||||||
searchView.queryHint = searchMenuItem.title
|
searchView.queryHint = searchMenuItem.title
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initSpinner() {
|
private fun initTabs() {
|
||||||
val entries = viewModel.availableScrobblers
|
val entries = viewModel.availableScrobblers
|
||||||
|
val tabs = binding.tabs
|
||||||
if (entries.size <= 1) {
|
if (entries.size <= 1) {
|
||||||
binding.spinnerScrobblers.isVisible = false
|
tabs.isVisible = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, entries)
|
val selectedId = arguments?.getInt(ARG_SCROBBLER, -1) ?: -1
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
tabs.removeAllTabs()
|
||||||
binding.spinnerScrobblers.adapter = adapter
|
tabs.clearOnTabSelectedListeners()
|
||||||
viewModel.selectedScrobblerIndex.observe(viewLifecycleOwner) {
|
tabs.addOnTabSelectedListener(this)
|
||||||
binding.spinnerScrobblers.setSelection(it)
|
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 {
|
companion object {
|
||||||
|
|
||||||
private const val TAG = "ScrobblingSelectorBottomSheet"
|
private const val TAG = "ScrobblingSelectorBottomSheet"
|
||||||
|
private const val ARG_SCROBBLER = "scrobbler"
|
||||||
|
|
||||||
fun show(fm: FragmentManager, manga: Manga) =
|
fun show(fm: FragmentManager, manga: Manga, scrobblerService: ScrobblerService?) =
|
||||||
ScrobblingSelectorBottomSheet().withArgs(1) {
|
ScrobblingSelectorBottomSheet().withArgs(2) {
|
||||||
putParcelable(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = false))
|
putParcelable(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = false))
|
||||||
|
if (scrobblerService != null) {
|
||||||
|
putInt(ARG_SCROBBLER, scrobblerService.id)
|
||||||
|
}
|
||||||
}.show(fm, TAG)
|
}.show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
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.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
@@ -46,7 +48,7 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|||||||
hasNextPage,
|
hasNextPage,
|
||||||
) { list, isHasNextPage ->
|
) { list, isHasNextPage ->
|
||||||
when {
|
when {
|
||||||
list.isEmpty() -> listOf()
|
list.isEmpty() -> listOf(emptyResultsHint())
|
||||||
isHasNextPage -> list + LoadingFooter
|
isHasNextPage -> list + LoadingFooter
|
||||||
else -> list
|
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
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,27 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||||
import kotlin.jvm.internal.Intrinsics
|
|
||||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
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.loadingFooterAD
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
||||||
|
import kotlin.jvm.internal.Intrinsics
|
||||||
|
|
||||||
class ScrobblerSelectorAdapter(
|
class ScrobblerSelectorAdapter(
|
||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
clickListener: OnListItemClickListener<ScrobblerManga>,
|
clickListener: OnListItemClickListener<ScrobblerManga>,
|
||||||
|
stateHolderListener: ListStateHolderListener,
|
||||||
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
|
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
delegatesManager.addDelegate(loadingStateAD())
|
delegatesManager.addDelegate(loadingStateAD())
|
||||||
.addDelegate(scrobblerMangaAD(lifecycleOwner, coil, clickListener))
|
.addDelegate(scrobblingMangaAD(lifecycleOwner, coil, clickListener))
|
||||||
.addDelegate(loadingFooterAD())
|
.addDelegate(loadingFooterAD())
|
||||||
|
.addDelegate(emptyHintAD(stateHolderListener))
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
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.newImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||||
|
|
||||||
fun scrobblerMangaAD(
|
fun scrobblingMangaAD(
|
||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
clickListener: OnListItemClickListener<ScrobblerManga>,
|
clickListener: OnListItemClickListener<ScrobblerManga>,
|
||||||
@@ -14,7 +14,6 @@ import androidx.core.view.postDelayed
|
|||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.TwoStatePreference
|
import androidx.preference.TwoStatePreference
|
||||||
import com.google.android.material.color.DynamicColors
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||||
@@ -56,7 +55,6 @@ class AppearanceSettingsFragment :
|
|||||||
entryValues = ListMode.values().names()
|
entryValues = ListMode.values().names()
|
||||||
setDefaultValueCompat(ListMode.GRID.name)
|
setDefaultValueCompat(ListMode.GRID.name)
|
||||||
}
|
}
|
||||||
findPreference<Preference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible = DynamicColors.isDynamicColorAvailable()
|
|
||||||
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
||||||
entryValues = resources.getStringArray(R.array.date_formats)
|
entryValues = resources.getStringArray(R.array.date_formats)
|
||||||
val now = Date().time
|
val now = Date().time
|
||||||
@@ -105,10 +103,7 @@ class AppearanceSettingsFragment :
|
|||||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_DYNAMIC_THEME -> {
|
AppSettings.KEY_COLOR_THEME,
|
||||||
postRestart()
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_THEME_AMOLED -> {
|
AppSettings.KEY_THEME_AMOLED -> {
|
||||||
postRestart()
|
postRestart()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
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.CacheDir
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository
|
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.mal.data.MALRepository
|
||||||
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
|
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
|
||||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
import org.koitharu.kotatsu.utils.FileSize
|
import org.koitharu.kotatsu.utils.FileSize
|
||||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -82,9 +86,9 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
bindShikimoriSummary()
|
bindScrobblerSummary(AppSettings.KEY_SHIKIMORI, shikimoriRepository)
|
||||||
bindMALSummary()
|
bindScrobblerSummary(AppSettings.KEY_ANILIST, aniListRepository)
|
||||||
bindAniListSummary()
|
bindScrobblerSummary(AppSettings.KEY_MAL, malRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
@@ -125,7 +129,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
|||||||
|
|
||||||
AppSettings.KEY_SHIKIMORI -> {
|
AppSettings.KEY_SHIKIMORI -> {
|
||||||
if (!shikimoriRepository.isAuthorized) {
|
if (!shikimoriRepository.isAuthorized) {
|
||||||
launchShikimoriAuth()
|
launchScrobblerAuth(shikimoriRepository)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
super.onPreferenceTreeClick(preference)
|
super.onPreferenceTreeClick(preference)
|
||||||
@@ -134,7 +138,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
|||||||
|
|
||||||
AppSettings.KEY_MAL -> {
|
AppSettings.KEY_MAL -> {
|
||||||
if (!malRepository.isAuthorized) {
|
if (!malRepository.isAuthorized) {
|
||||||
launchMALAuth()
|
launchScrobblerAuth(malRepository)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
super.onPreferenceTreeClick(preference)
|
super.onPreferenceTreeClick(preference)
|
||||||
@@ -143,7 +147,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
|||||||
|
|
||||||
AppSettings.KEY_ANILIST -> {
|
AppSettings.KEY_ANILIST -> {
|
||||||
if (!aniListRepository.isAuthorized) {
|
if (!aniListRepository.isAuthorized) {
|
||||||
launchAniListAuth()
|
launchScrobblerAuth(aniListRepository)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
super.onPreferenceTreeClick(preference)
|
super.onPreferenceTreeClick(preference)
|
||||||
@@ -213,54 +217,35 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindShikimoriSummary() {
|
private fun bindScrobblerSummary(key: String, repository: ScrobblerRepository) {
|
||||||
findPreference<Preference>(AppSettings.KEY_SHIKIMORI)?.summary = if (shikimoriRepository.isAuthorized) {
|
val pref = findPreference<Preference>(key) ?: return
|
||||||
getString(R.string.logged_in_as, shikimoriRepository.cachedUser?.nickname)
|
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 {
|
} 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() {
|
private fun launchScrobblerAuth(repository: ScrobblerRepository) {
|
||||||
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() {
|
|
||||||
runCatching {
|
runCatching {
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
intent.data = Uri.parse(shikimoriRepository.oauthUrl)
|
intent.data = Uri.parse(repository.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)
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Snackbar.make(listView, it.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
|
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
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||||
android:id="@+id/dragHandle"
|
android:id="@+id/dragHandle"
|
||||||
@@ -35,6 +36,17 @@
|
|||||||
tools:background="@sample/covers[9]"
|
tools:background="@sample/covers[9]"
|
||||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
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
|
<TextView
|
||||||
android:id="@+id/textView_title"
|
android:id="@+id/textView_title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -103,7 +115,6 @@
|
|||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:lineSpacingMultiplier="1.2"
|
android:lineSpacingMultiplier="1.2"
|
||||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
|
|||||||
@@ -24,11 +24,13 @@
|
|||||||
|
|
||||||
</org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar>
|
</org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar>
|
||||||
|
|
||||||
<Spinner
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/spinner_scrobblers"
|
android:id="@+id/tabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
@@ -36,7 +38,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:padding="@dimen/grid_spacing"
|
android:padding="@dimen/grid_spacing"
|
||||||
android:scrollbarStyle="outsideOverlay"
|
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/item_manga_list" />
|
tools:listitem="@layout/item_manga_list" />
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_shiki_track"
|
android:id="@+id/action_scrobbling"
|
||||||
android:orderInCategory="50"
|
android:orderInCategory="50"
|
||||||
android:title="@string/tracking"
|
android:title="@string/tracking"
|
||||||
app:showAsAction="never" />
|
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="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="colorSurface">@color/surface_amoled</item>
|
||||||
<item name="android:colorBackground">@color/background_amoled</item>
|
<item name="android:colorBackground">@color/background_amoled</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Kotatsu.Monet.Amoled">
|
</resources>
|
||||||
<item name="colorSurface">@color/surface_amoled</item>
|
|
||||||
<item name="android:colorBackground">@color/background_amoled</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</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="sliderPreferenceStyle" />
|
||||||
<attr name="multiAutoCompleteTextViewPreferenceStyle" />
|
<attr name="multiAutoCompleteTextViewPreferenceStyle" />
|
||||||
<attr name="autoCompleteTextViewPreferenceStyle" />
|
<attr name="autoCompleteTextViewPreferenceStyle" />
|
||||||
|
<attr name="themeChooserPreferenceStyle" />
|
||||||
<attr name="listItemTextViewStyle" />
|
<attr name="listItemTextViewStyle" />
|
||||||
<attr name="fastScrollerStyle" />
|
<attr name="fastScrollerStyle" />
|
||||||
|
|
||||||
@@ -75,4 +76,14 @@
|
|||||||
<attr name="fitStatusBar" format="boolean" />
|
<attr name="fitStatusBar" format="boolean" />
|
||||||
</declare-styleable>
|
</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>
|
</resources>
|
||||||
|
|||||||
@@ -4,4 +4,5 @@
|
|||||||
<bool name="light_status_bar">true</bool>
|
<bool name="light_status_bar">true</bool>
|
||||||
<bool name="light_navigation_bar">false</bool>
|
<bool name="light_navigation_bar">false</bool>
|
||||||
<bool name="com_samsung_android_icon_container_has_icon_container">true</bool>
|
<bool name="com_samsung_android_icon_container_has_icon_container">true</bool>
|
||||||
|
<bool name="is_color_themes_available">false</bool>
|
||||||
</resources>
|
</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">Enable logging</string>
|
||||||
<string name="enable_logging_summary">Record some actions for debug purposes</string>
|
<string name="enable_logging_summary">Record some actions for debug purposes</string>
|
||||||
<string name="show_suspicious_content">Show suspicious content</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>
|
</resources>
|
||||||
|
|||||||
@@ -255,6 +255,11 @@
|
|||||||
<item name="android:widgetLayout">@layout/preference_widget_material_switch</item>
|
<item name="android:widgetLayout">@layout/preference_widget_material_switch</item>
|
||||||
</style>
|
</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 -->
|
<!-- Progress drawable -->
|
||||||
|
|
||||||
<style name="ProgressDrawable">
|
<style name="ProgressDrawable">
|
||||||
|
|||||||
@@ -87,12 +87,10 @@
|
|||||||
<!-- Monet theme only support S+ -->
|
<!-- Monet theme only support S+ -->
|
||||||
<style name="Theme.Kotatsu.Monet" />
|
<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" parent="ThemeOverlay.Material3.Light" />
|
||||||
|
|
||||||
|
<style name="ThemeOverlay.Kotatsu.Amoled" parent="" />
|
||||||
|
|
||||||
<style name="Theme.Kotatsu.Dialog" parent="">
|
<style name="Theme.Kotatsu.Dialog" parent="">
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowIsFloating">true</item>
|
<item name="android:windowIsFloating">true</item>
|
||||||
|
|||||||
@@ -11,12 +11,10 @@
|
|||||||
android:title="@string/theme"
|
android:title="@string/theme"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<org.koitharu.kotatsu.settings.utils.ThemeChooserPreference
|
||||||
android:defaultValue="false"
|
android:key="color_theme"
|
||||||
android:key="dynamic_theme"
|
android:title="@string/color_theme"
|
||||||
android:summary="@string/dynamic_theme_summary"
|
app:isPreferenceVisible="@bool/is_color_themes_available" />
|
||||||
android:title="@string/dynamic_theme"
|
|
||||||
app:isPreferenceVisible="false" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
@@ -26,7 +24,8 @@
|
|||||||
|
|
||||||
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
||||||
android:key="app_locale"
|
android:key="app_locale"
|
||||||
android:title="@string/language" />
|
android:title="@string/language"
|
||||||
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="date_format"
|
android:key="date_format"
|
||||||
@@ -36,7 +35,6 @@
|
|||||||
android:entries="@array/list_modes"
|
android:entries="@array/list_modes"
|
||||||
android:key="list_mode_2"
|
android:key="list_mode_2"
|
||||||
android:title="@string/list_mode"
|
android:title="@string/list_mode"
|
||||||
app:allowDividerAbove="true"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||||
|
|||||||
Reference in New Issue
Block a user