Skip to content

Commit fced2f1

Browse files
Merge pull request #14799 from woocommerce/issue/WOOMOB-1347_add_note_fragment
[WOOMOB-1347] - Booking details note - new screen to manage the note
2 parents ac9bbc5 + d66ec63 commit fced2f1

File tree

13 files changed

+586
-16
lines changed

13 files changed

+586
-16
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/BookingsRepository.kt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.woocommerce.android.WooException
44
import com.woocommerce.android.tools.SelectedSite
55
import kotlinx.coroutines.flow.Flow
66
import kotlinx.coroutines.flow.flowOf
7+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingUpdatePayload
78
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption
89
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsOrderOption
910
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsStore
@@ -62,6 +63,13 @@ class BookingsRepository @Inject constructor(
6263
bookingId = bookingId
6364
)
6465

66+
suspend fun getBooking(bookingId: Long): Booking? {
67+
return bookingsStore.getBooking(
68+
site = selectedSite.get(),
69+
bookingId = bookingId
70+
)
71+
}
72+
6573
suspend fun fetchBooking(
6674
bookingId: Long
6775
): Result<Booking> {
@@ -105,10 +113,26 @@ class BookingsRepository @Inject constructor(
105113
bookingId: Long,
106114
attendanceStatus: BookingEntity.AttendanceStatus,
107115
): Result<Unit> {
108-
val result = bookingsStore.updateAttendanceStatus(
116+
val result = bookingsStore.updateBooking(
117+
site = selectedSite.get(),
118+
bookingId = bookingId,
119+
bookingUpdatePayload = BookingUpdatePayload(attendanceStatus = attendanceStatus)
120+
)
121+
return if (result.isError) {
122+
Result.failure(WooException(result.error))
123+
} else {
124+
Result.success(Unit)
125+
}
126+
}
127+
128+
suspend fun updateNote(
129+
bookingId: Long,
130+
note: String,
131+
): Result<Unit> {
132+
val result = bookingsStore.updateBooking(
109133
site = selectedSite.get(),
110134
bookingId = bookingId,
111-
attendanceStatus = attendanceStatus
135+
bookingUpdatePayload = BookingUpdatePayload(note = note)
112136
)
113137
return if (result.isError) {
114138
Result.failure(WooException(result.error))

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/details/BookingDetailsFragment.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.view.View
66
import android.view.ViewGroup
77
import androidx.fragment.app.viewModels
88
import androidx.navigation.fragment.findNavController
9+
import androidx.navigation.fragment.navArgs
910
import com.woocommerce.android.ui.base.BaseFragment
1011
import com.woocommerce.android.ui.base.UIMessageResolver
1112
import com.woocommerce.android.ui.compose.composeView
@@ -21,6 +22,7 @@ class BookingDetailsFragment : BaseFragment() {
2122
lateinit var uiMessageResolver: UIMessageResolver
2223

2324
private val viewModel: BookingDetailsViewModel by viewModels()
25+
private val args: BookingDetailsFragmentArgs by navArgs()
2426

2527
override val activityAppBarStatus: AppBarStatus
2628
get() = AppBarStatus.Hidden
@@ -35,6 +37,12 @@ class BookingDetailsFragment : BaseFragment() {
3537
BookingDetailsFragmentDirections
3638
.actionBookingDetailsFragmentToOrderDetailFragment(orderId)
3739
)
40+
},
41+
onViewNotes = {
42+
findNavController().navigate(
43+
BookingDetailsFragmentDirections
44+
.actionBookingDetailsFragmentToBookingNoteFragment(args.bookingId)
45+
)
3846
}
3947
)
4048
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/details/BookingDetailsScreen.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
5353
fun BookingDetailsScreen(
5454
viewModel: BookingDetailsViewModel,
5555
onBack: () -> Unit,
56-
onViewOrder: (Long) -> Unit
56+
onViewOrder: (Long) -> Unit,
57+
onViewNotes: () -> Unit,
5758
) {
5859
val viewState by viewModel.state.observeAsState()
5960

@@ -62,6 +63,7 @@ fun BookingDetailsScreen(
6263
viewState = it,
6364
onBack = onBack,
6465
onViewOrder = onViewOrder,
66+
onViewNotes = onViewNotes,
6567
)
6668
}
6769
}
@@ -72,6 +74,7 @@ fun BookingDetailsScreen(
7274
viewState: BookingDetailsViewState,
7375
onBack: () -> Unit,
7476
onViewOrder: (Long) -> Unit,
77+
onViewNotes: () -> Unit,
7578
) {
7679
val showAttendanceSheet = remember { mutableStateOf(false) }
7780
Scaffold(
@@ -103,6 +106,7 @@ fun BookingDetailsScreen(
103106
onCancelBooking = viewState.onCancelBooking,
104107
onViewOrder = onViewOrder,
105108
onAttendanceStatusClicked = { showAttendanceSheet.value = true },
109+
onViewNotes = onViewNotes,
106110
)
107111
}
108112
}
@@ -126,6 +130,7 @@ private fun BookingDetailsContent(
126130
onCancelBooking: () -> Unit,
127131
onViewOrder: (Long) -> Unit,
128132
onAttendanceStatusClicked: () -> Unit,
133+
onViewNotes: () -> Unit,
129134
) {
130135
BookingSummary(
131136
model = booking.bookingSummary,
@@ -158,7 +163,7 @@ private fun BookingDetailsContent(
158163
}
159164
BookingNoteSection(
160165
note = booking.note,
161-
onClick = {},
166+
onClick = onViewNotes,
162167
modifier = Modifier.fillMaxWidth()
163168
)
164169
}
@@ -262,7 +267,8 @@ private fun BookingDetailsPreview() {
262267
),
263268
),
264269
onBack = {},
265-
onViewOrder = {}
270+
onViewOrder = {},
271+
onViewNotes = {},
266272
)
267273
}
268274
}
@@ -278,6 +284,7 @@ private fun BookingDetailsLoadingPreview() {
278284
),
279285
onBack = {},
280286
onViewOrder = {},
287+
onViewNotes = {},
281288
)
282289
}
283290
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.woocommerce.android.ui.bookings.note
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.fragment.app.viewModels
8+
import androidx.navigation.fragment.findNavController
9+
import com.woocommerce.android.ui.base.BaseFragment
10+
import com.woocommerce.android.ui.base.UIMessageResolver
11+
import com.woocommerce.android.ui.compose.composeView
12+
import com.woocommerce.android.ui.main.AppBarStatus
13+
import com.woocommerce.android.viewmodel.MultiLiveEvent
14+
import dagger.hilt.android.AndroidEntryPoint
15+
import javax.inject.Inject
16+
17+
@AndroidEntryPoint
18+
class BookingNoteFragment : BaseFragment() {
19+
20+
@Inject
21+
lateinit var uiMessageResolver: UIMessageResolver
22+
23+
private val viewModel: BookingNoteViewModel by viewModels()
24+
25+
override val activityAppBarStatus: AppBarStatus
26+
get() = AppBarStatus.Hidden
27+
28+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
29+
return composeView {
30+
BookingNoteScreen(
31+
viewModel = viewModel,
32+
onBack = { findNavController().popBackStack() },
33+
)
34+
}
35+
}
36+
37+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
38+
super.onViewCreated(view, savedInstanceState)
39+
handleEvents()
40+
}
41+
42+
private fun handleEvents() {
43+
viewModel.event.observe(viewLifecycleOwner) { event ->
44+
when (event) {
45+
is MultiLiveEvent.Event.ShowSnackbar -> {
46+
uiMessageResolver.showSnack(event.message)
47+
}
48+
is MultiLiveEvent.Event.Exit -> {
49+
findNavController().navigateUp()
50+
}
51+
}
52+
}
53+
}
54+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.woocommerce.android.ui.bookings.note
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.fillMaxSize
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.foundation.layout.size
8+
import androidx.compose.foundation.text.BasicTextField
9+
import androidx.compose.material3.CircularProgressIndicator
10+
import androidx.compose.material3.MaterialTheme
11+
import androidx.compose.material3.Scaffold
12+
import androidx.compose.material3.Text
13+
import androidx.compose.runtime.Composable
14+
import androidx.compose.runtime.LaunchedEffect
15+
import androidx.compose.runtime.getValue
16+
import androidx.compose.runtime.livedata.observeAsState
17+
import androidx.compose.runtime.mutableStateOf
18+
import androidx.compose.runtime.remember
19+
import androidx.compose.runtime.rememberUpdatedState
20+
import androidx.compose.runtime.saveable.rememberSaveable
21+
import androidx.compose.runtime.setValue
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.draw.shadow
24+
import androidx.compose.ui.focus.FocusRequester
25+
import androidx.compose.ui.focus.focusRequester
26+
import androidx.compose.ui.graphics.SolidColor
27+
import androidx.compose.ui.res.stringResource
28+
import androidx.compose.ui.text.TextRange
29+
import androidx.compose.ui.text.input.TextFieldValue
30+
import androidx.compose.ui.unit.dp
31+
import com.woocommerce.android.R
32+
import com.woocommerce.android.ui.compose.component.Toolbar
33+
import com.woocommerce.android.ui.compose.component.WCTextButton
34+
35+
@Composable
36+
fun BookingNoteScreen(
37+
viewModel: BookingNoteViewModel,
38+
onBack: () -> Unit,
39+
) {
40+
val viewState by viewModel.state.observeAsState()
41+
viewState?.let {
42+
BookingNoteScreen(
43+
viewState = it,
44+
onBack = onBack,
45+
)
46+
}
47+
}
48+
49+
@Composable
50+
fun BookingNoteScreen(
51+
viewState: BookingNoteViewState,
52+
onBack: () -> Unit,
53+
) {
54+
val focusRequester = remember { FocusRequester() }
55+
56+
// Request focus only after the note is available in the composition
57+
LaunchedEffect(viewState.editedNote != null) {
58+
focusRequester.requestFocus()
59+
}
60+
61+
Scaffold(
62+
topBar = {
63+
Toolbar(
64+
title = stringResource(R.string.booking_note_screen_title),
65+
onNavigationButtonClick = onBack,
66+
actions = {
67+
if (viewState.isSaveVisible) {
68+
WCTextButton(
69+
onClick = viewState.onSaveClicked,
70+
enabled = viewState.isSaveEnabled,
71+
) {
72+
when (viewState.noteSaveStatus) {
73+
is NoteSaveStatus.Idle -> {
74+
Text(stringResource(R.string.booking_note_screen_done))
75+
}
76+
77+
is NoteSaveStatus.InProgress -> {
78+
CircularProgressIndicator(
79+
modifier = Modifier.size(24.dp)
80+
)
81+
}
82+
}
83+
}
84+
}
85+
},
86+
modifier = Modifier.shadow(4.dp)
87+
)
88+
}
89+
) { innerPadding ->
90+
Box(
91+
modifier = Modifier
92+
.background(MaterialTheme.colorScheme.surface)
93+
.padding(top = innerPadding.calculateTopPadding())
94+
) {
95+
viewState.editedNote?.let {
96+
NoteTextField(
97+
value = it,
98+
onValueChange = viewState.onNoteChange,
99+
enabled = viewState.noteEditable,
100+
modifier = Modifier
101+
.fillMaxSize()
102+
.padding(horizontal = 16.dp, vertical = 12.dp)
103+
.focusRequester(focusRequester)
104+
)
105+
}
106+
}
107+
}
108+
}
109+
110+
@Composable
111+
private fun NoteTextField(
112+
value: String,
113+
onValueChange: (String) -> Unit,
114+
enabled: Boolean,
115+
modifier: Modifier = Modifier,
116+
) {
117+
var textFieldValueState by rememberSaveable(stateSaver = TextFieldValue.Saver) {
118+
mutableStateOf(
119+
TextFieldValue(
120+
text = value,
121+
selection = TextRange(value.length)
122+
)
123+
)
124+
}
125+
126+
val textFieldValue = textFieldValueState.copy(text = value)
127+
128+
val lastValue by rememberUpdatedState(value)
129+
130+
BasicTextField(
131+
value = textFieldValue,
132+
onValueChange = {
133+
textFieldValueState = it
134+
if (it.text != lastValue) {
135+
// Update external value when changed
136+
onValueChange(it.text)
137+
}
138+
},
139+
enabled = enabled,
140+
modifier = modifier,
141+
textStyle = MaterialTheme.typography.bodyLarge.copy(
142+
color = MaterialTheme.colorScheme.onSurface
143+
),
144+
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary)
145+
)
146+
}

0 commit comments

Comments
 (0)