Skip to content

Commit

Permalink
[23356]
Browse files Browse the repository at this point in the history
- [TripGov5] update WaupacaHomeActivity to load AppColors from preferences on app load
- [TripGov5] update views to implement dynamic colors using custom binding set
- [TripKitUI] update views to implement dynamic colors using custom binding set
  • Loading branch information
MichaelReyes committed Feb 19, 2025
1 parent 907d0f7 commit da5c0e0
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
Expand Down Expand Up @@ -42,6 +43,10 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
Expand Down Expand Up @@ -241,7 +246,8 @@ class TimetableFragment : BaseTripKitPagerFragment(), View.OnClickListener {
}

smoothScroller.targetPosition = integer.toInt()
binding.recyclerView.layoutManager?.startSmoothScroll(smoothScroller)
// binding.recyclerView.layoutManager?.startSmoothScroll(smoothScroller)
scrollToNowPosition(2000L)
}.addTo(autoDisposable)

val buffer = if (tripSegment == null) {
Expand Down Expand Up @@ -475,16 +481,7 @@ class TimetableFragment : BaseTripKitPagerFragment(), View.OnClickListener {
tripSegment = _tripSegment

binding.goToNowButton.setOnClickListener {
val layoutManager = binding.recyclerView.layoutManager as LinearLayoutManager
val firstNowPosition = viewModel.getFirstNowPosition()
if (layoutManager.findFirstVisibleItemPosition() < firstNowPosition &&
firstNowPosition != 0 &&
(firstNowPosition + 1) < (binding.recyclerView.adapter?.itemCount ?: 0)
) {
binding.recyclerView.scrollToPosition(firstNowPosition + 1)
} else {
binding.recyclerView.scrollToPosition(firstNowPosition)
}
scrollToNowPosition()
}

bookingActions =
Expand All @@ -508,6 +505,37 @@ class TimetableFragment : BaseTripKitPagerFragment(), View.OnClickListener {

}

private fun scrollToNowPosition(loadDelay: Long = 0) {
lifecycleScope.launch {
delay(loadDelay)
withContext(Dispatchers.Main) {
val layoutManager = binding.recyclerView.layoutManager as LinearLayoutManager
val firstNowPosition = viewModel.getFirstNowPosition()
val itemCount = binding.recyclerView.adapter?.itemCount ?: 0

if (firstNowPosition in 0 until itemCount) {
// If it's not at the bottom, make sure it stays at the top
if (firstNowPosition < itemCount - 1) {
layoutManager.scrollToPositionWithOffset(firstNowPosition, 0)
} else {
// If it's the last item, just scroll smoothly to it
binding.recyclerView.smoothScrollToPosition(firstNowPosition)
}
}
/*val layoutManager = binding.recyclerView.layoutManager as LinearLayoutManager
val firstNowPosition = viewModel.getFirstNowPosition()
if (layoutManager.findFirstVisibleItemPosition() < firstNowPosition &&
firstNowPosition != 0 &&
(firstNowPosition + 1) < (binding.recyclerView.adapter?.itemCount ?: 0)
) {
binding.recyclerView.scrollToPosition(firstNowPosition + 1)
} else {
binding.recyclerView.scrollToPosition(firstNowPosition)
}*/
}
}
}

override fun onStop() {
super.onStop()
viewModel.stopRealtime()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,9 @@ class TimetableViewModel @Inject constructor(

private fun getFirstNowPosition(services: List<ServiceViewModel>): Int {
return services.indexOfFirst {
it.getRealTimeDeparture() - getNow.execute().toSeconds() >= -1L
val condition = it.getRealTimeDeparture() - getNow.execute().toSeconds() >= -1L
println("tag123: ${it.secondaryText.get()} ~ ${it.getRealTimeDeparture() - getNow.execute().toSeconds()}")
condition
}.let { max(it, 0) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.skedgo.tripkit.ui.utils
import android.content.res.ColorStateList
import android.content.res.Resources.NotFoundException
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.drawable.Drawable
Expand All @@ -14,12 +15,17 @@ import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.widget.Button
import android.widget.CalendarView
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.RadioButton
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.appcompat.widget.SwitchCompat
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat
import androidx.databinding.BindingAdapter
import com.afollestad.materialdialogs.utils.MDUtil.ifNotZero
import com.bumptech.glide.load.model.GlideUrl
Expand Down Expand Up @@ -298,6 +304,10 @@ fun TextView.setDrawableTint(color: Int) {
}
}

/**
* =================== Dynamic color binding using colors from [DynamicAppColor] ===================
*/

@BindingAdapter("appTint")
fun setButtonAppTint(button: Button, @ColorInt default: Int) {
DynamicAppColor.getAppColors()?.tintColor?.let {
Expand All @@ -320,26 +330,33 @@ fun setButtonStateBackground(button: Button, @ColorInt default: Int?) {

val disabledColor = enabledColor?.let {
ColorUtils.setAlphaComponent(it, (0.3 * 255).toInt()) // 30% opacity for disabled state
} ?: Color.GRAY // Fallback to gray if everything is null

val enabledDrawable = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 16f // Adjust as needed
setColor(enabledColor ?: Color.TRANSPARENT)
}

val disabledDrawable = GradientDrawable().apply {
} ?: Color.GRAY // Fallback to gray if null

val colorStateList = ColorStateList(
arrayOf(
intArrayOf(-android.R.attr.state_enabled), // Disabled state
intArrayOf(android.R.attr.state_enabled) // Enabled state
),
intArrayOf(
disabledColor, // Color when disabled
enabledColor ?: Color.TRANSPARENT // Color when enabled
)
)

// Create a GradientDrawable to ensure background supports tinting
val backgroundDrawable = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 16f
setColor(disabledColor)
cornerRadius = button.resources.getDimension(R.dimen.button_radius_small) // Adjust as needed
setColor(enabledColor ?: Color.TRANSPARENT) // Set initial color
}

val stateListDrawable = StateListDrawable().apply {
addState(intArrayOf(-android.R.attr.state_enabled), disabledDrawable) // Disabled state
addState(intArrayOf(), enabledDrawable) // Default state
}
// Set the background explicitly so tinting works
button.background = backgroundDrawable
button.backgroundTintList = colorStateList

button.background = stateListDrawable
// Ensure state change is reflected
button.invalidate()
button.refreshDrawableState()
}

@BindingAdapter("appTint")
Expand All @@ -349,11 +366,11 @@ fun setImageViewAppTint(imageView: ImageView, @ColorInt default: Int?) {
} ?: DynamicAppColor.getSystemColors()?.tintColor ?: default.takeIf { default != 0 }

color?.let {
imageView.setColorFilter(it, PorterDuff.Mode.SRC_IN) // Apply tint only to fill
imageView.setColorFilter(it, PorterDuff.Mode.SRC_IN)
}
}

@BindingAdapter("appTint")
@BindingAdapter("appTextTint")
fun setTextViewAppTint(textView: TextView, @ColorInt default: Int?) {
val color = DynamicAppColor.getAppColors()?.tintColor?.let {
Color.rgb(it.red, it.green, it.blue)
Expand All @@ -375,6 +392,86 @@ fun setLayoutAppTint(view: View, @ColorInt default: Int?) {
}
}

@BindingAdapter("appSwitchTint")
fun setSwitchCheckedTint(switch: SwitchCompat, @ColorInt default: Int?) {
val checkedColor = DynamicAppColor.getAppColors()?.tintColor?.let {
Color.rgb(it.red, it.green, it.blue)
} ?: DynamicAppColor.getSystemColors()?.tintColor ?: default

val uncheckedColor = checkedColor?.let {
ColorUtils.setAlphaComponent(it, (0.4 * 255).toInt()) // 40% opacity for unchecked state
} ?: Color.GRAY // Fallback to gray if everything is null

val thumbStates = ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_checked), // Checked state
intArrayOf() // Default (unchecked) state
),
intArrayOf(
checkedColor ?: Color.TRANSPARENT, // Thumb color when checked
uncheckedColor // Thumb color when unchecked
)
)

val trackStates = ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_checked), // Checked state
intArrayOf() // Default (unchecked) state
),
intArrayOf(
ColorUtils.setAlphaComponent(checkedColor ?: Color.TRANSPARENT, (0.6 * 255).toInt()), // Track when checked
ColorUtils.setAlphaComponent(uncheckedColor, (0.3 * 255).toInt()) // Track when unchecked
)
)

switch.thumbTintList = thumbStates
switch.trackTintList = trackStates
}

@BindingAdapter("appProgressTint")
fun setProgressBarTint(progressBar: ProgressBar, @ColorInt default: Int?) {
val color = DynamicAppColor.getAppColors()?.tintColor?.let {
Color.rgb(it.red, it.green, it.blue)
} ?: DynamicAppColor.getSystemColors()?.tintColor ?: default

color?.let {
val colorStateList = ColorStateList.valueOf(it)
progressBar.indeterminateDrawable?.setTintList(colorStateList) // For indeterminate mode
progressBar.progressDrawable?.setTintList(colorStateList) // For determinate mode
}
}

@BindingAdapter("appCalendarSelectedTint")
fun setCalendarViewSelectedTint(calendarView: CalendarView, @ColorInt default: Int?) {
val color = DynamicAppColor.getAppColors()?.tintColor?.let {
Color.rgb(it.red, it.green, it.blue)
} ?: DynamicAppColor.getSystemColors()?.tintColor ?: default

color?.let {
try {
// Change the selected date text color (requires reflection)
val field = CalendarView::class.java.getDeclaredField("mDaySelectorPaint")
field.isAccessible = true
val paint = field.get(calendarView) as Paint
paint.color = it
calendarView.invalidate() // Refresh the UI
} catch (e: Exception) {
e.printStackTrace()
}

// Set the selected date background indicator (vertical bar)
try {
val field = CalendarView::class.java.getDeclaredField("mSelectedDateVerticalBar")
field.isAccessible = true
val drawable = ContextCompat.getDrawable(calendarView.context, field.getInt(calendarView))
drawable?.setTint(it)
field.set(calendarView, drawable)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

@BindingAdapter("appTextTint")
fun setButtonTextAppTint(button: Button, @ColorInt default: Int?) {
val color = DynamicAppColor.getAppColors()?.tintColor?.let {
Expand Down Expand Up @@ -412,6 +509,37 @@ fun setTextViewBackgroundTint(textView: TextView, @ColorInt default: Int?) {
}
}

@BindingAdapter("appCheckedTint")
fun setRadioButtonCheckedTint(radioButton: RadioButton, @ColorInt default: Int?) {
val color = DynamicAppColor.getAppColors()?.tintColor?.let {
Color.rgb(it.red, it.green, it.blue)
} ?: DynamicAppColor.getSystemColors()?.tintColor ?: default.takeIf { default != 0 }

color?.let {
val colorStateList = ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_checked), // Checked state
intArrayOf(-android.R.attr.state_checked) // Unchecked state
),
intArrayOf(
it, // Checked color
ColorUtils.setAlphaComponent(it, (0.2 * 255).toInt()) // Unchecked color (60% opacity)
)
)

// Set button tint
ViewCompat.setBackgroundTintList(radioButton, colorStateList)

// Apply the color manually to the compound button drawable
radioButton.compoundDrawablesRelative.forEach { drawable ->
drawable?.setTintList(colorStateList)
}

// Force UI refresh
radioButton.invalidate()
}
}

@BindingAdapter("appForeground")
fun setTextViewAppForegroundTint(textView: TextView, @ColorInt default: Int?) {
val color = DynamicAppColor.getAppColors()?.barForeground?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@
android:checked="@{viewModel.isLeaveAfter}"
android:onClick="@{handlers::onClickLeaveAfter}"
android:text="@{viewModel.leaveAtLabel}"
tools:text="@string/leave_at" />
tools:text="@string/leave_at"
app:appCheckedTint="@{null}"/>

<RadioButton
android:id="@+id/radio_arrive_by"
style="@style/FlatRadioButtonStyle"
android:checked="@{!viewModel.isLeaveAfter}"
android:onClick="@{handlers::onClickArriveBy}"
android:text="@{viewModel.arriveByLabel}"
tools:text="@string/arrive_by" />
tools:text="@string/arrive_by"
app:appCheckedTint="@{null}"/>
</RadioGroup>

<TextView
Expand Down
15 changes: 9 additions & 6 deletions TripKitAndroidUI/src/main/res/layout/dialog_generic_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
android:id="@+id/genericList_cl_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:padding="@dimen/spacing_small"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toTopOf="parent"
app:appBackground="@{@color/colorPrimary}"
tools:background="@color/colorPrimary">

<TextView
android:id="@+id/genericList_tv_title"
Expand All @@ -35,6 +36,7 @@
android:gravity="center"
android:paddingStart="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_normal"
android:textSize="@dimen/font_size_medium"
android:text="@{viewModel.title}"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
Expand All @@ -54,11 +56,11 @@
android:paddingStart="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_normal"
android:text="@string/close"
android:textColor="@android:color/white"
app:clickWithDebounce="@{() -> handler.onClose()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:appForeground="@{@android:color/white}"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Expand Down Expand Up @@ -106,7 +108,7 @@

</LinearLayout>

<androidx.appcompat.widget.AppCompatButton
<Button
android:id="@+id/genericList_b_confirm"
android:layout_width="match_parent"
android:layout_height="50dp"
Expand All @@ -119,7 +121,8 @@
android:theme="@style/Theme.MaterialComponents"
android:visibility="@{!viewModel.viewModeOnly}"
app:clickWithDebounce="@{() -> handler.onConfirm()}"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"
app:appTint="@{@color/colorPrimary}"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Expand Down
Loading

0 comments on commit da5c0e0

Please sign in to comment.