-
Notifications
You must be signed in to change notification settings - Fork 493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compose wrapper #331
Comments
That seems like a great idea, I have not had the time to work with compose, so feel free to take it up. |
Initial code: @Composable
fun CalendarView(
modifier: Modifier = Modifier,
currentMonth: YearMonth,
firstMonth: YearMonth,
lastMonth: YearMonth,
firstDayOfWeek: DayOfWeek,
daySize: Size = CalendarView.SIZE_SQUARE,
scrollMode: ScrollMode = ScrollMode.CONTINUOUS,
orientation: Orientation = Orientation.VERTICAL,
maxRowCount: Int = 6,
inDateStyle: InDateStyle = InDateStyle.ALL_MONTHS,
outDateStyle: OutDateStyle = OutDateStyle.END_OF_ROW,
hasBoundaries: Boolean = true,
wrappedPageHeightAnimationDuration: Int = 200,
calendarViewState: CalendarViewState = rememberCalendarViewState(),
monthScrollEvent: ((CalendarMonth) -> Unit)? = null,
monthHeaderContent: (@Composable (CalendarMonth) -> Unit)? = null,
monthFooterContent: (@Composable (CalendarMonth) -> Unit)? = null,
dayContent: @Composable (CalendarDay) -> Unit,
) {
var firstMonthState by remember {
mutableStateOf(firstMonth)
}
var lastMonthState by remember {
mutableStateOf(lastMonth)
}
AndroidView(
modifier = modifier,
factory = { context ->
CalendarView(context).apply {
calendarViewState.calendarView = WeakReference(this)
dayViewResource = R.layout.compose_view_layout
dayBinder = object : DayBinder<ComposeViewContainer> {
override fun create(view: View) = ComposeViewContainer(view)
override fun bind(container: ComposeViewContainer, day: CalendarDay) {
container.composeView.setContent {
dayContent(day)
}
}
}
if (monthHeaderContent != null) {
monthHeaderResource = R.layout.compose_view_layout
monthHeaderBinder = object : MonthHeaderFooterBinder<ComposeViewContainer> {
override fun create(view: View) = ComposeViewContainer(view)
override fun bind(container: ComposeViewContainer, month: CalendarMonth) {
container.composeView.setContent {
monthHeaderContent(month)
}
}
}
}
if (monthFooterContent != null) {
monthFooterResource = R.layout.compose_view_layout
monthFooterBinder = object : MonthHeaderFooterBinder<ComposeViewContainer> {
override fun create(view: View) = ComposeViewContainer(view)
override fun bind(container: ComposeViewContainer, month: CalendarMonth) {
container.composeView.setContent {
monthFooterContent(month)
}
}
}
}
this.daySize = daySize
this.orientation = when (orientation) {
Orientation.VERTICAL -> RecyclerView.VERTICAL
Orientation.HORIZONTAL -> RecyclerView.HORIZONTAL
}
this.scrollMode = scrollMode
this.maxRowCount = maxRowCount
this.inDateStyle = inDateStyle
this.outDateStyle = outDateStyle
this.hasBoundaries = hasBoundaries
this.wrappedPageHeightAnimationDuration = wrappedPageHeightAnimationDuration
monthScrollListener = monthScrollEvent
setup(firstMonth, lastMonth, firstDayOfWeek)
scrollToMonth(currentMonth)
}
},
update = { calendarView ->
calendarView.daySize = daySize
calendarView.orientation = when (orientation) {
Orientation.VERTICAL -> RecyclerView.VERTICAL
Orientation.HORIZONTAL -> RecyclerView.HORIZONTAL
}
calendarView.scrollMode = scrollMode
calendarView.maxRowCount = maxRowCount
calendarView.inDateStyle = inDateStyle
calendarView.outDateStyle = outDateStyle
calendarView.hasBoundaries = hasBoundaries
if (firstMonthState != firstMonth || lastMonthState != lastMonth) {
firstMonthState = firstMonth
lastMonthState = lastMonth
calendarView.updateMonthRange(firstMonth, lastMonth)
}
})
}
class ComposeViewContainer(view: View) : ViewContainer(view) {
val composeView: ComposeView = view.findViewById(R.id.composeView)
}
enum class Orientation {
VERTICAL,
HORIZONTAL,
}
@Composable
fun rememberCalendarViewState(): CalendarViewState {
return remember {
CalendarViewState()
}
}
class CalendarViewState internal constructor(){
internal var calendarView: WeakReference<CalendarView>? = null
fun scrollToMonth(month: YearMonth) {
calendarView?.get()?.scrollToMonth(month)
}
fun smoothScrollToMonth(month: YearMonth) {
calendarView?.get()?.smoothScrollToMonth(month)
}
fun scrollToDay(day: CalendarDay) {
calendarView?.get()?.scrollToDay(day)
}
fun scrollToDate(date: LocalDate, owner: DayOwner = DayOwner.THIS_MONTH) {
calendarView?.get()?.scrollToDate(date, owner)
}
fun smoothScrollToDay(day: CalendarDay) {
calendarView?.get()?.smoothScrollToDay(day)
}
fun smoothScrollToDate(date: LocalDate, owner: DayOwner = DayOwner.THIS_MONTH) {
calendarView?.get()?.smoothScrollToDate(date, owner)
}
} And sample usage: @Composable
fun MainScreen() {
var selectedDay by remember {
mutableStateOf<LocalDate?>(null)
}
val calendarViewState = rememberCalendarViewState()
CalendarView(
modifier = Modifier.fillMaxSize(),
currentMonth = YearMonth.now(),
firstMonth = YearMonth.now().minusMonths(10),
lastMonth = YearMonth.now().plusMonths(10),
firstDayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek,
scrollMode = ScrollMode.PAGED,
orientation = Orientation.HORIZONTAL,
calendarViewState = calendarViewState,
dayContent = { day ->
Box(
modifier = Modifier
.fillMaxSize()
.clickable {
if (selectedDay != day.date) {
selectedDay = day.date
} else if (selectedDay == day.date) {
selectedDay = null
}
}, contentAlignment = Alignment.Center
) {
val color = when {
selectedDay == day.date -> Color.Red
day.owner == DayOwner.THIS_MONTH -> Color.Black
else -> Color.Gray
}
Text(text = day.day.toString(), color = color)
}
},
monthHeaderContent = { calendarMonth ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(onClick = {
calendarViewState.smoothScrollToMonth(
calendarMonth.yearMonth.minusMonths(1)
)
}) {
Text(text = "Previous")
}
Text(text = "Month: ${calendarMonth.month}")
Button(onClick = {
calendarViewState.smoothScrollToMonth(
calendarMonth.yearMonth.plusMonths(1)
)
}) {
Text(text = "Next")
}
}
},
)
} I'm not sure about the notification of view about the changes. This code seems to work fine and all of the views gets updated just because the I could use some helping hand in testing this in a wild! |
Looks good from my point of view but I have not used compose intensively to say for sure. However, it does look like the calendar properties cannot be updated individually without recreating the entire thing (this includes internal month generation etc), is this efficient? |
I saw that in set(value) {
if (field != value) {
field = value
updateAdapterViewConfig()
}
} So for those properties this should make no effect really. For now i see no performance problems, but that's why i could use some helping hand here. Maybe somebody have some heavy use that i'm missing here |
Yep, you are right that it will not trigger new updates. I'll get on testing this a bit later (anyone can also test if interested), then we'll see how we can move forward with the feature. Thanks a lot for contributing. |
@jakoss There are some more pointers here. This has to be added too:
But currently the binders do not have the corresponding recycle method, so this can't be called. The API has to be updated. Also:
|
@ammargitham Thanks, i indeed have to take this into account. The issue is that |
@jakoss But |
@kizitonwose I'm not 100% sure if i understand correctly, but the outcome for me is the same - to properly handle composable here we need some kind of callback on |
To update the thread - we found that performance of my solution was not acceptable, every click had it's delay and we couldn't really find reason why it was happening. Fully compose alternative showed up fortunately: https://github.com/boguszpawlowski/ComposeCalendar . So far it's working for us just fine |
Have you considered creating a compose wrapper over
CalendarView
? I'm not talking about total rewrite, but about a separate artifact withCalendarView
as composable function. It would be nothing more then a wrapper usingAndroidView
, but until some new fully compose calendar will stabilize - this should work just fine.If you're fine with that i think i'll try and find some time to create a PR with that
The text was updated successfully, but these errors were encountered: