diff --git a/app/src/main/java/org/wikipedia/main/MainFragment.kt b/app/src/main/java/org/wikipedia/main/MainFragment.kt index e2f0042b818..9107be6d7d5 100644 --- a/app/src/main/java/org/wikipedia/main/MainFragment.kt +++ b/app/src/main/java/org/wikipedia/main/MainFragment.kt @@ -93,6 +93,7 @@ import org.wikipedia.views.TabCountsView import org.wikipedia.views.imageservice.ImageService import org.wikipedia.watchlist.WatchlistActivity import org.wikipedia.yearinreview.YearInReviewOnboardingActivity +import org.wikipedia.yearinreview.YearInReviewSurvey import java.io.File import java.util.concurrent.TimeUnit @@ -213,6 +214,7 @@ class MainFragment : Fragment(), BackPressedHandler, MenuProvider, FeedFragment. downloadReceiver.register(requireContext(), downloadReceiverCallback) // reset the last-page-viewed timer Prefs.pageLastShown = 0 + YearInReviewSurvey.maybeShowYearInReviewFeedbackDialog(requireActivity()) } override fun onDestroyView() { diff --git a/app/src/main/java/org/wikipedia/page/PageActivity.kt b/app/src/main/java/org/wikipedia/page/PageActivity.kt index d6032cb4579..df15814e0db 100644 --- a/app/src/main/java/org/wikipedia/page/PageActivity.kt +++ b/app/src/main/java/org/wikipedia/page/PageActivity.kt @@ -86,6 +86,7 @@ import org.wikipedia.views.FrameLayoutNavMenuTriggerer import org.wikipedia.views.ObservableWebView import org.wikipedia.views.ViewUtil import org.wikipedia.watchlist.WatchlistExpiry +import org.wikipedia.yearinreview.YearInReviewSurvey import java.util.Locale class PageActivity : BaseActivity(), PageFragment.Callback, LinkPreviewDialog.LoadPageCallback, FrameLayoutNavMenuTriggerer.Callback { @@ -328,6 +329,7 @@ class PageActivity : BaseActivity(), PageFragment.Callback, LinkPreviewDialog.Lo app.resetWikiSite() updateNotificationsButton(false) Prefs.temporaryWikitext = null + YearInReviewSurvey.maybeShowYearInReviewFeedbackDialog(this) } override fun onPause() { diff --git a/app/src/main/java/org/wikipedia/settings/Prefs.kt b/app/src/main/java/org/wikipedia/settings/Prefs.kt index d246dd5a811..ff6df998d7c 100644 --- a/app/src/main/java/org/wikipedia/settings/Prefs.kt +++ b/app/src/main/java/org/wikipedia/settings/Prefs.kt @@ -30,6 +30,7 @@ import org.wikipedia.util.ReleaseUtil.isDevRelease import org.wikipedia.util.StringUtil import org.wikipedia.watchlist.WatchlistFilterTypes import org.wikipedia.yearinreview.YearInReviewModel +import org.wikipedia.yearinreview.YearInReviewSurveyState import java.util.Date /** Shared preferences utility for convenient POJO access. */ @@ -788,9 +789,11 @@ object Prefs { get() = PrefsIoUtil.getBoolean(R.string.preference_key_year_in_review_visited, false) set(value) = PrefsIoUtil.setBoolean(R.string.preference_key_year_in_review_visited, value) - var yearInReviewSurveyShown - get() = PrefsIoUtil.getBoolean(R.string.preference_key_yir_survey_shown, false) - set(value) = PrefsIoUtil.setBoolean(R.string.preference_key_yir_survey_shown, value) + var yearInReviewSurveyState: YearInReviewSurveyState + get() = PrefsIoUtil.getString(R.string.preference_key_yir_survey_state, null)?.let { + YearInReviewSurveyState.valueOf(it) + } ?: YearInReviewSurveyState.NOT_TRIGGERED + set(value) = PrefsIoUtil.setString(R.string.preference_key_yir_survey_state, value.name) var isRecommendedReadingListEnabled get() = PrefsIoUtil.getBoolean(R.string.preference_key_recommended_reading_list_enabled, false) diff --git a/app/src/main/java/org/wikipedia/settings/dev/DeveloperSettingsPreferenceLoader.kt b/app/src/main/java/org/wikipedia/settings/dev/DeveloperSettingsPreferenceLoader.kt index 0cb8b214cda..83b9df7b1e0 100644 --- a/app/src/main/java/org/wikipedia/settings/dev/DeveloperSettingsPreferenceLoader.kt +++ b/app/src/main/java/org/wikipedia/settings/dev/DeveloperSettingsPreferenceLoader.kt @@ -34,6 +34,7 @@ import org.wikipedia.setupLeakCanary import org.wikipedia.suggestededits.provider.EditingSuggestionsProvider import org.wikipedia.util.FeedbackUtil import org.wikipedia.util.StringUtil.fromHtml +import org.wikipedia.yearinreview.YearInReviewSurveyState internal class DeveloperSettingsPreferenceLoader(fragment: PreferenceFragmentCompat) : BasePreferenceLoader(fragment) { private val setMediaWikiBaseUriChangeListener = Preference.OnPreferenceChangeListener { _, _ -> @@ -257,6 +258,22 @@ internal class DeveloperSettingsPreferenceLoader(fragment: PreferenceFragmentCom fragment.requireActivity().finish() true } + (findPreference(R.string.preference_key_yir_survey_state) as ListPreference).apply { + val states = YearInReviewSurveyState.entries + val names = states.map { it.name }.toTypedArray() + entries = names + entryValues = names + setOnPreferenceChangeListener { _, newValue -> + val selectedState = newValue as String + val source = when (selectedState) { + "NOT_TRIGGERED" -> YearInReviewSurveyState.NOT_TRIGGERED + "SHOULD_SHOW" -> YearInReviewSurveyState.SHOULD_SHOW + else -> YearInReviewSurveyState.SHOWN + } + Prefs.yearInReviewSurveyState = source + true + } + } } private fun setUpMediaWikiSettings() { diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewActivity.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewActivity.kt index 4129f720061..f45e4df1a39 100644 --- a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewActivity.kt +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewActivity.kt @@ -19,6 +19,7 @@ import org.wikipedia.analytics.eventplatform.BreadCrumbLogEvent import org.wikipedia.analytics.eventplatform.DonorExperienceEvent import org.wikipedia.analytics.eventplatform.EventPlatformClient import org.wikipedia.compose.theme.BaseTheme +import org.wikipedia.settings.Prefs class YearInReviewActivity : BaseActivity() { @@ -49,13 +50,23 @@ class YearInReviewActivity : BaseActivity() { YearInReviewScreenDeck( state = screenState, requestScreenshotBitmap = { width, height -> viewModel.requestScreenshotHeaderBitmap(width, height) }, - onBackButtonClick = { + onCloseButtonClick = { + if (viewModel.slideViewedCount >= YearInReviewViewModel.MIN_SLIDES_BEFORE_SURVEY && Prefs.yearInReviewSurveyState == YearInReviewSurveyState.NOT_TRIGGERED) { + Prefs.yearInReviewSurveyState = YearInReviewSurveyState.SHOULD_SHOW + } finish() }, - onNextButtonClick = { pagerState -> + onNextButtonClick = { pagerState, currentSlideData -> + viewModel.slideViewedCount += 1 coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } + if (currentSlideData is YearInReviewScreenData.HighlightsScreen) { + if (Prefs.yearInReviewSurveyState == YearInReviewSurveyState.NOT_TRIGGERED) { + Prefs.yearInReviewSurveyState = YearInReviewSurveyState.SHOULD_SHOW + } + finish() + } }, onDonateClick = { EventPlatformClient.submit( diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt index ef92439ae59..0ead5d10614 100644 --- a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt @@ -78,8 +78,8 @@ fun YearInReviewScreenDeck( state: UiState>, requestScreenshotBitmap: ((Int, Int) -> Bitmap)?, onDonateClick: () -> Unit, - onNextButtonClick: (PagerState) -> Unit, - onBackButtonClick: () -> Unit + onNextButtonClick: (PagerState, YearInReviewScreenData) -> Unit, + onCloseButtonClick: () -> Unit ) { when (state) { is UiState.Loading -> { @@ -121,7 +121,7 @@ fun YearInReviewScreenDeck( containerColor = WikipediaTheme.colors.paperColor), title = { }, navigationIcon = { - IconButton(onClick = { onBackButtonClick() }) { + IconButton(onClick = { onCloseButtonClick() }) { Icon( painter = painterResource(R.drawable.ic_close_black_24dp), tint = WikipediaTheme.colors.primaryColor, @@ -162,7 +162,7 @@ fun YearInReviewScreenDeck( bottomBar = { MainBottomBar( pages, - onNavigationRightClick = { onNextButtonClick(pagerState) }, + onNavigationRightClick = { onNextButtonClick(pagerState, pages[pagerState.currentPage]) }, pagerState = pagerState, totalPages = pages.size, onShareClick = { @@ -553,8 +553,8 @@ fun PreviewStandardContent() { )), requestScreenshotBitmap = null, onDonateClick = {}, - onBackButtonClick = {}, - onNextButtonClick = {} + onCloseButtonClick = {}, + onNextButtonClick = { _, _ -> } ) } } @@ -577,8 +577,8 @@ fun PreviewReadingPatternsContent() { )), requestScreenshotBitmap = null, onDonateClick = {}, - onBackButtonClick = {}, - onNextButtonClick = {} + onCloseButtonClick = {}, + onNextButtonClick = { _, _ -> } ) } } diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSurvey.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSurvey.kt index c2bb79b8569..c6e02d6af61 100644 --- a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSurvey.kt +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSurvey.kt @@ -1,264 +1,76 @@ package org.wikipedia.yearinreview -import androidx.annotation.StringRes -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.selection.selectableGroup -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.AlertDialogDefaults -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.RadioButtonColors -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import android.app.Activity +import android.widget.ScrollView +import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.wikipedia.R -import org.wikipedia.compose.theme.BaseTheme -import org.wikipedia.compose.theme.WikipediaTheme -import org.wikipedia.theme.Theme +import org.wikipedia.databinding.DialogFeedbackOptionsBinding +import org.wikipedia.settings.Prefs +import org.wikipedia.util.FeedbackUtil -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun YearInReviewSurvey( - onSubmitButtonClick: (String, String) -> Unit, - onCancelButtonClick: () -> Unit -) { - val radioOptions = listOf( - stringResource(R.string.year_in_review_survey_very_satisfied), - stringResource(R.string.year_in_review_survey_satisfied), - stringResource(R.string.year_in_review_survey_neutral), - stringResource(R.string.year_in_review_survey_unsatisfied), - stringResource(R.string.year_in_review_survey_very_unsatisfied) - ) +object YearInReviewSurvey { + fun maybeShowYearInReviewFeedbackDialog(activity: Activity) { + if (Prefs.yearInReviewSurveyState != YearInReviewSurveyState.SHOULD_SHOW) { + return + } + val binding = DialogFeedbackOptionsBinding.inflate(activity.layoutInflater) + binding.titleText.text = activity.getString(R.string.year_in_review_survey_title) + binding.messageText.text = activity.getString(R.string.year_in_review_survey_subtitle) + binding.feedbackInputContainer.isVisible = true + binding.optionVerySatisfied.isVisible = true + binding.optionVeryUnsatisfied.isVisible = true + binding.feedbackInputContainer.hint = + activity.getString(R.string.year_in_review_survey_placeholder_text) - val (selectedOption, onOptionSelected) = remember { mutableStateOf(radioOptions[2]) } - var userInput by remember { mutableStateOf("") } - val scrollState = rememberScrollState() + val dialog = MaterialAlertDialogBuilder(activity) + .setView(binding.root) + .setCancelable(false) + .create() - BasicAlertDialog( - onDismissRequest = { /* no dismissal unless SurveyButton used */ } - ) { - Surface( - tonalElevation = AlertDialogDefaults.TonalElevation, - modifier = Modifier - .clip(shape = RoundedCornerShape(28.dp)) - .background(color = WikipediaTheme.colors.paperColor) - .fillMaxWidth() - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .verticalScroll(scrollState) - .background(color = WikipediaTheme.colors.paperColor) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - ) { - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier - .wrapContentHeight() - .align(Alignment.Center) - ) { - Text( - text = stringResource(R.string.year_in_review_survey_title), - style = MaterialTheme.typography.titleLarge.copy( - fontSize = 22.sp, - fontWeight = FontWeight.Bold - ), - color = WikipediaTheme.colors.primaryColor, - modifier = Modifier.padding(top = 24.dp) - ) - Text( - text = stringResource(R.string.year_in_review_survey_subtitle), - style = MaterialTheme.typography.bodyLarge, - color = WikipediaTheme.colors.secondaryColor, - modifier = Modifier.padding(bottom = 12.dp) - ) - } - HorizontalDivider( - modifier = Modifier - .height(1.dp) - .fillMaxWidth() - .align(Alignment.BottomCenter), - color = WikipediaTheme.colors.inactiveColor - ) - } - Column( - modifier = Modifier.selectableGroup() - ) { - radioOptions.forEach { text -> - Row( - modifier = Modifier - .fillMaxWidth() - .height(56.dp) - .selectable( - selected = (text == selectedOption), - onClick = { onOptionSelected(text) }, - role = Role.RadioButton - ) - .padding(start = 8.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - RadioButton( - selected = (text == selectedOption), - colors = RadioButtonColors( - selectedColor = WikipediaTheme.colors.progressiveColor, - unselectedColor = WikipediaTheme.colors.progressiveColor, - disabledSelectedColor = WikipediaTheme.colors.inactiveColor, - disabledUnselectedColor = WikipediaTheme.colors.inactiveColor - ), - onClick = null, - modifier = Modifier - .padding(9.dp) - .scale(1.2f) - ) - Text( - text = text, - style = MaterialTheme.typography.bodyLarge, - color = WikipediaTheme.colors.primaryColor, - ) - } - } - } - TextField( - value = userInput, - onValueChange = { userInput = it }, - textStyle = MaterialTheme.typography.bodyLarge, - colors = TextFieldDefaults.colors( - focusedIndicatorColor = WikipediaTheme.colors.progressiveColor, - unfocusedIndicatorColor = WikipediaTheme.colors.progressiveColor, - focusedContainerColor = WikipediaTheme.colors.backgroundColor, - unfocusedContainerColor = WikipediaTheme.colors.backgroundColor, - focusedTextColor = WikipediaTheme.colors.primaryColor - ), - placeholder = { - if (userInput.isEmpty()) { - Text( - text = stringResource(R.string.year_in_review_survey_placeholder_text), - color = WikipediaTheme.colors.secondaryColor, - style = MaterialTheme.typography.bodyLarge - ) - } - }, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) - ) - Box( - modifier = Modifier - .fillMaxWidth() - .height(72.dp) - ) { - HorizontalDivider( - modifier = Modifier - .height(1.dp) - .fillMaxWidth() - .align(Alignment.TopCenter), - color = WikipediaTheme.colors.inactiveColor - ) - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .align(Alignment.Center) - .padding(start = 16.dp, end = 24.dp, top = 16.dp, bottom = 24.dp) - ) { - SurveyButton( - buttonText = R.string.year_in_review_survey_cancel, - onClick = { onCancelButtonClick() } - ) - Spacer(modifier = Modifier.width(8.dp)) - SurveyButton( - buttonText = R.string.year_in_review_survey_submit, - onClick = { onSubmitButtonClick(selectedOption, userInput) } - ) + binding.cancelButton.setOnClickListener { + dialog.dismiss() + } + binding.submitButton.setOnClickListener { + val selectedOption = getSelectedOption(binding) + val feedbackText = binding.feedbackInput.text.toString() + // @TODO: instrumentation + FeedbackUtil.showMessage(activity, R.string.survey_dialog_submitted_snackbar) + dialog.dismiss() + } + + binding.feedbackInput.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) { + binding.dialogContainer.postDelayed({ + if (!activity.isDestroyed) { + binding.dialogContainer.fullScroll(ScrollView.FOCUS_DOWN) } - } + }, 200) } } + dialog.show() + Prefs.yearInReviewSurveyState = YearInReviewSurveyState.SHOWN } -} -@Composable -fun SurveyButton( - @StringRes buttonText: Int, - onClick: () -> Unit, -) { - Button( - onClick = onClick, - shape = RectangleShape, - colors = ButtonDefaults.buttonColors( - containerColor = WikipediaTheme.colors.paperColor, - ), - contentPadding = PaddingValues(horizontal = 12.dp), - ) { - Text( - text = stringResource(buttonText), - style = MaterialTheme.typography.titleMedium, - color = WikipediaTheme.colors.progressiveColor - ) + private fun getSelectedOption(binding: DialogFeedbackOptionsBinding): Int? { + val selectedId = binding.feedbackRadioGroup.checkedRadioButtonId + return when (selectedId) { + R.id.optionVerySatisfied -> 1 + R.id.optionSatisfied -> 2 + R.id.optionNeutral -> 3 + R.id.optionUnsatisfied -> 4 + R.id.optionVeryUnsatisfied -> 5 + else -> null + } } -} -@Preview -@Composable -fun PreviewSurvey() { - BaseTheme(currentTheme = Theme.LIGHT) { - Column( - modifier = Modifier - .fillMaxSize() - .background(WikipediaTheme.colors.paperColor) - ) { - YearInReviewSurvey( - onSubmitButtonClick = { _, _ -> - /*No logic, preview only*/ - }, - onCancelButtonClick = { /*No logic, preview only*/ }, - ) - } + fun resetYearInReviewSurveyState() { + Prefs.yearInReviewSurveyState = YearInReviewSurveyState.NOT_TRIGGERED } } + +enum class YearInReviewSurveyState { + NOT_TRIGGERED, + SHOULD_SHOW, + SHOWN +} diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewViewModel.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewViewModel.kt index 25e81c1320d..574c8226cdd 100644 --- a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewViewModel.kt +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewViewModel.kt @@ -44,6 +44,8 @@ class YearInReviewViewModel() : ViewModel() { var screenshotHeaderBitmap = createBitmap(1, 1) + var slideViewedCount = 1 + init { fetchPersonalizedData() } @@ -262,6 +264,7 @@ class YearInReviewViewModel() : ViewModel() { ) Prefs.yearInReviewModelData = yearInReviewModelMap + YearInReviewSurvey.resetYearInReviewSurveyState() } val yearInReviewModel = yearInReviewModelMap[YIR_YEAR]!! @@ -304,6 +307,7 @@ class YearInReviewViewModel() : ViewModel() { const val MIN_READING_MINUTES = 1 const val MIN_ARTICLES_PER_MAP_CLUSTER = 2 const val MAX_ARTICLES_ON_MAP = 32 + const val MIN_SLIDES_BEFORE_SURVEY = 2 // Whether Year-in-Review should be accessible at all. // (different from the user enabling/disabling it in Settings.) diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 353f20861ca..2e4737a1dc8 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -185,7 +185,7 @@ isOtdSoundOn isYearInReviewEnabled yearInReviewVisited - yirSurveyShown + yirSurveyState selectedAppIcon recommendedReadingListSettings recommendedReadingListEnabled diff --git a/app/src/main/res/xml/developer_preferences.xml b/app/src/main/res/xml/developer_preferences.xml index 97fea5a94c0..80a3f7f285d 100644 --- a/app/src/main/res/xml/developer_preferences.xml +++ b/app/src/main/res/xml/developer_preferences.xml @@ -525,9 +525,9 @@ android:key="@string/preference_key_year_in_review_visited" android:title="@string/preference_key_year_in_review_visited" /> - +