Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8d2040b
YiR: Pre-design scaffolding
cooltey Sep 19, 2025
0916538
Merge branch 'main' into yir-re-design
cooltey Sep 19, 2025
350cf1b
Wire up API calls and put TODOs
cooltey Sep 19, 2025
f0557c0
Put async for api calls
cooltey Sep 19, 2025
6f2b7fb
Merge branch 'main' into yir-re-design
cooltey Sep 22, 2025
42f85eb
Format for the headline/body text
cooltey Sep 23, 2025
e385bb0
Revert "Format for the headline/body text"
cooltey Sep 23, 2025
84b73de
Separate initial screen out
cooltey Sep 23, 2025
26655a5
Onboarding
cooltey Sep 24, 2025
344f904
Simplify a bit
cooltey Sep 24, 2025
43700b5
Initial thought about indivial screens
cooltey Sep 24, 2025
9d93c41
Merge branch 'main' into yir-re-design
cooltey Sep 24, 2025
cdb2548
Add temporary functions for slides
cooltey Sep 24, 2025
9cbbf1f
Review comment
cooltey Sep 24, 2025
259209c
clean up async
cooltey Sep 24, 2025
d7237fc
Fix error
cooltey Sep 24, 2025
7595e56
Consolidate routes
cooltey Sep 25, 2025
65409d7
Update a bit
cooltey Sep 25, 2025
ce28f83
Merge branch 'main' into yir-re-design
cooltey Sep 25, 2025
d4c4592
Remove survey for now and refine the finish action for onboarding and
cooltey Sep 25, 2025
18256af
Fix preview
cooltey Sep 25, 2025
1aff666
YiR: update entry point in More menu. (#5964)
dbrant Sep 25, 2025
f8c7114
YiR: Login dialog in the onboarding screen. (#5965)
dbrant Sep 25, 2025
711a974
Model class
cooltey Sep 25, 2025
1379200
use data class
cooltey Sep 25, 2025
095c7b5
Merge branch 'main' into yir-re-design
cooltey Sep 25, 2025
785e946
Create dataModel for YiR
cooltey Sep 25, 2025
20f226a
Update model
cooltey Sep 25, 2025
3fec482
Update model class and add todos
cooltey Sep 25, 2025
b2a8abb
Update query for top visited articles
cooltey Sep 25, 2025
d8d3cd2
No longer use CenterAlignedTopAppBar with W icon.
dbrant Sep 26, 2025
4266944
Merge branch 'main' into yir-re-design
cooltey Sep 29, 2025
3529736
Wire up onboarding screen to PageActivity, with result.
dbrant Sep 29, 2025
c1fa000
Merge branch 'yir-re-design' of github.com:wikimedia/apps-android-wik…
dbrant Sep 29, 2025
246b99e
Update naming and fix lint
cooltey Sep 29, 2025
45e73cf
Add two parameters for YearInReviewSlides
cooltey Sep 30, 2025
a129b12
Clean up outdated strings
cooltey Sep 30, 2025
e898995
Add filterNotNull() for slide routes
cooltey Sep 30, 2025
1f121e1
Fix translation test
cooltey Oct 1, 2025
a3ee3a7
YiR: save YiR model data to preference (#5967)
cooltey Oct 1, 2025
1e43871
YiR: Create Most visited Categories slide (#5968)
cooltey Oct 1, 2025
b16c57d
YiR: Create Your Top Articles slide (#5969)
cooltey Oct 1, 2025
8be18fc
Merge branch 'main' into yir-re-design
cooltey Oct 2, 2025
cf67139
Fix translation
cooltey Oct 2, 2025
ef1576d
YiR: Create Viewed Edits slide (#5970)
cooltey Oct 3, 2025
a323891
YiR: Add English most popular articles slide (#5971)
cooltey Oct 3, 2025
44fcdc6
Merge branch 'main' into yir-re-design
cooltey Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@
<activity
android:name=".settings.dev.playground.CategoryDeveloperPlayGround"/>

<activity
android:name=".yearinreview.YearInReviewOnboardingActivity"/>

<activity
android:name=".yearinreview.YearInReviewActivity"/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ class ActivityTabViewModel() : ViewModel() {
val totalTimeSpent = AppDatabase.instance.historyEntryWithImageDao().getTimeSpentSinceTimeStamp(weekAgo)

val thirtyDaysAgo = now - TimeUnit.DAYS.toMillis(30)
val articlesReadThisMonth = AppDatabase.instance.historyEntryDao().getDistinctEntriesSince(thirtyDaysAgo) ?: 0
val articlesReadThisMonth = AppDatabase.instance.historyEntryDao().getDistinctEntriesCountSince(thirtyDaysAgo) ?: 0
val articlesReadByWeek = mutableListOf<Int>()
articlesReadByWeek.add(AppDatabase.instance.historyEntryDao().getDistinctEntriesSince(weekAgo) ?: 0)
articlesReadByWeek.add(AppDatabase.instance.historyEntryDao().getDistinctEntriesCountSince(weekAgo) ?: 0)
for (i in 1..3) {
weekAgo -= weekInMillis
val articlesRead = AppDatabase.instance.historyEntryDao().getDistinctEntriesBetween(weekAgo, weekAgo + weekInMillis)
val articlesRead = AppDatabase.instance.historyEntryDao().getDistinctEntriesCountBetween(weekAgo, weekAgo + weekInMillis)
articlesReadByWeek.add(articlesRead)
}
val mostRecentReadTime = AppDatabase.instance.historyEntryDao().getMostRecentEntry()?.timestamp?.toInstant()?.atZone(ZoneId.systemDefault())?.toLocalDateTime()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ interface CategoryDao {
@Query("SELECT year, month, title, lang, SUM (count) AS count FROM Category WHERE year = :year AND month = :month GROUP BY title, lang ORDER BY count DESC")
suspend fun getTopCategoriesByMonth(year: Int, month: Int): List<Category>

// Get top categories by year
@Query("SELECT year, month, title, lang, SUM (count) AS count FROM Category WHERE year = :year GROUP BY title, lang ORDER BY count DESC LIMIT :limit")
suspend fun getTopCategoriesByYear(year: Int, limit: Int): List<Category>

@Query("SELECT * FROM Category")
suspend fun getAllCategories(): List<Category>

Expand Down
12 changes: 6 additions & 6 deletions app/src/main/java/org/wikipedia/history/db/HistoryEntryDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ interface HistoryEntryDao {
suspend fun findEntryBy(authority: String, lang: String, apiTitle: String, timestamp: Long): HistoryEntry?

@Query("SELECT COUNT(*) FROM (SELECT DISTINCT HistoryEntry.lang, HistoryEntry.apiTitle FROM HistoryEntry WHERE timestamp BETWEEN :startDate AND :endDate)")
suspend fun getDistinctEntriesBetween(startDate: Long?, endDate: Long?): Int
suspend fun getDistinctEntriesCountBetween(startDate: Long?, endDate: Long?): Int

@Query("SELECT DISTINCT displayTitle FROM HistoryEntry LIMIT 3")
suspend fun getDisplayTitles(): List<String>
@Query("SELECT COUNT(*) FROM (SELECT DISTINCT HistoryEntry.lang, HistoryEntry.apiTitle FROM HistoryEntry WHERE timestamp > :timestamp)")
suspend fun getDistinctEntriesCountSince(timestamp: Long): Int?

@Query("SELECT displayTitle FROM HistoryEntry WHERE timestamp > :timestamp GROUP BY displayTitle ORDER BY COUNT(displayTitle) DESC LIMIT :limit")
suspend fun getTopVisitedEntriesSince(limit: Int, timestamp: Long): List<String>

@Query("SELECT COUNT(*) FROM HistoryEntry")
suspend fun getHistoryCount(): Int
Expand All @@ -39,9 +42,6 @@ interface HistoryEntryDao {
@Query("DELETE FROM HistoryEntry WHERE authority = :authority AND lang = :lang AND namespace = :namespace AND apiTitle = :apiTitle")
suspend fun deleteBy(authority: String, lang: String, namespace: String?, apiTitle: String)

@Query("SELECT COUNT(*) FROM (SELECT DISTINCT HistoryEntry.lang, HistoryEntry.apiTitle FROM HistoryEntry WHERE timestamp > :timestamp)")
suspend fun getDistinctEntriesSince(timestamp: Long): Int?

@Query("SELECT * FROM HistoryEntry ORDER BY timestamp DESC LIMIT 1")
suspend fun getMostRecentEntry(): HistoryEntry?

Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/org/wikipedia/login/LoginActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ class LoginActivity : BaseActivity() {
const val SOURCE_SUGGESTED_EDITS = "suggestededits"
const val SOURCE_TALK = "talk"
const val SOURCE_ACTIVITY_TAB = "activity_tab"
const val SOURCE_YEAR_IN_REVIEW = "yir"

fun newIntent(context: Context, source: String, createAccountFirst: Boolean = true): Intent {
return Intent(context, LoginActivity::class.java)
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/org/wikipedia/main/MainFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ import org.wikipedia.views.NotificationButtonView
import org.wikipedia.views.TabCountsView
import org.wikipedia.views.imageservice.ImageService
import org.wikipedia.watchlist.WatchlistActivity
import org.wikipedia.yearinreview.YearInReviewEntryDialog
import org.wikipedia.yearinreview.YearInReviewOnboardingActivity
import java.io.File
import java.util.concurrent.TimeUnit

Expand Down Expand Up @@ -499,7 +499,7 @@ class MainFragment : Fragment(), BackPressedHandler, MenuProvider, FeedFragment.
}

override fun yearInReviewClick() {
ExclusiveBottomSheetPresenter.show(childFragmentManager, YearInReviewEntryDialog.newInstance())
startActivity(YearInReviewOnboardingActivity.newIntent(requireActivity()))
}

fun setBottomNavVisible(visible: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class MenuNavTabDialog : ExtendedBottomSheetDialogFragment() {
callback()?.yearInReviewClick()
dismiss()
}
binding.yearInReviewRedDot.isVisible = !Prefs.yearInReviewVisited

binding.mainDrawerEditContainer.setOnClickListener {
BreadCrumbLogEvent.logClick(requireActivity(), binding.mainDrawerEditContainer)
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/org/wikipedia/page/PageActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.YearInReviewOnboardingActivity
import java.util.Locale

class PageActivity : BaseActivity(), PageFragment.Callback, LinkPreviewDialog.LoadPageCallback, FrameLayoutNavMenuTriggerer.Callback {
Expand Down Expand Up @@ -179,6 +180,12 @@ class PageActivity : BaseActivity(), PageFragment.Callback, LinkPreviewDialog.Lo
}
}

private val yearInReviewLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_CANCELED) {
FeedbackUtil.showMessage(this, getString(R.string.year_in_review_get_started_later))
}
}

public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!DeviceUtil.assertAppContext(this)) {
Expand Down Expand Up @@ -283,6 +290,8 @@ class PageActivity : BaseActivity(), PageFragment.Callback, LinkPreviewDialog.Lo
// then we must have been launched with an Intent, so... handle it!
handleIntent(intent)
}

maybeShowYearInReview()
}

override fun onStart() {
Expand Down Expand Up @@ -706,6 +715,12 @@ class PageActivity : BaseActivity(), PageFragment.Callback, LinkPreviewDialog.Lo
.show()
}

private fun maybeShowYearInReview() {
if (Prefs.isYearInReviewEnabled && !Prefs.yearInReviewVisited) {
yearInReviewLauncher.launch((YearInReviewOnboardingActivity.newIntent(this)))
}
}

private fun maybeShowThemeTooltip() {
if (!Prefs.showOneTimeCustomizeToolbarTooltip) {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ interface ReadingListPageDao {
@Query("SELECT * FROM ReadingListPage WHERE atime > 0 ORDER BY atime DESC LIMIT :limit OFFSET :offset")
suspend fun getPagesByLocallySavedTime(limit: Int, offset: Int): List<ReadingListPage>

@Query("SELECT DISTINCT displayTitle FROM ReadingListPage ORDER BY atime DESC LIMIT :limit")
suspend fun getLatestArticleTitles(limit: Int): List<String>

suspend fun getAllPagesToBeSaved() = getPagesByStatus(ReadingListPage.STATUS_QUEUE_FOR_SAVE, true)

suspend fun getAllPagesToBeForcedSave() = getPagesByStatus(ReadingListPage.STATUS_QUEUE_FOR_FORCED_SAVE, true)
Expand Down
12 changes: 11 additions & 1 deletion app/src/main/java/org/wikipedia/settings/Prefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.wikipedia.util.DateUtil.dbDateParse
import org.wikipedia.util.ReleaseUtil.isDevRelease
import org.wikipedia.util.StringUtil
import org.wikipedia.watchlist.WatchlistFilterTypes
import org.wikipedia.yearinreview.YearInReviewModel
import java.util.Date

/** Shared preferences utility for convenient POJO access. */
Expand Down Expand Up @@ -783,7 +784,11 @@ object Prefs {
get() = PrefsIoUtil.getBoolean(R.string.preference_key_year_in_review_is_enabled, false)
set(value) = PrefsIoUtil.setBoolean(R.string.preference_key_year_in_review_is_enabled, value)

var yirSurveyShown
var yearInReviewVisited: Boolean
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)

Expand Down Expand Up @@ -858,4 +863,9 @@ object Prefs {
var isActivityTabOnboardingShown
get() = PrefsIoUtil.getBoolean(R.string.preference_key_activity_tab_onboarding_shown, false)
set(value) = PrefsIoUtil.setBoolean(R.string.preference_key_activity_tab_onboarding_shown, value)

var yearInReviewModelData
get() = JsonUtil.decodeFromString<Map<Int, YearInReviewModel>>(PrefsIoUtil.getString(R.string.preference_key_yir_model_data, null))
?: emptyMap()
set(modelDataWithYear) = PrefsIoUtil.setString(R.string.preference_key_yir_model_data, JsonUtil.encodeToString(modelDataWithYear))
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ internal class DeveloperSettingsPreferenceLoader(fragment: PreferenceFragmentCom
fragment.requireActivity().finish()
true
}
findPreference(R.string.preference_key_yir_model_data).onPreferenceClickListener = Preference.OnPreferenceClickListener {
Prefs.yearInReviewModelData = emptyMap()
Toast.makeText(activity, "Year in Review data has been reset", Toast.LENGTH_SHORT).show()
true
}
}

private fun setUpMediaWikiSettings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,15 @@ import androidx.activity.viewModels
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.wikipedia.activity.BaseActivity
import org.wikipedia.analytics.eventplatform.BreadCrumbLogEvent
import org.wikipedia.analytics.eventplatform.DonorExperienceEvent
import org.wikipedia.analytics.eventplatform.EventPlatformClient
import org.wikipedia.analytics.eventplatform.PatrollerExperienceEvent
import org.wikipedia.compose.theme.BaseTheme
import org.wikipedia.settings.Prefs

Expand All @@ -33,68 +27,27 @@ class YearInReviewActivity : BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Prefs.yearInReviewVisited = true

setContent {
BaseTheme {
val coroutineScope = rememberCoroutineScope()
val navController = rememberNavController()
var isSurveyVisible by remember { mutableStateOf(false) }

BackHandler {
if (viewModel.canShowSurvey) {
isSurveyVisible = true
} else {
endYearInReviewActivity(coroutineScope, this)
}
finish()
}

if (isSurveyVisible) {
Prefs.yirSurveyShown = true
YearInReviewSurvey(
onCancelButtonClick = {
isSurveyVisible = false
endYearInReviewActivity(coroutineScope, this)
},
onSubmitButtonClick = { selectedOption, userInput ->
PatrollerExperienceEvent.logAction(
action = "yir_survey_submit",
activeInterface = "yir_survey_form",
actionData = PatrollerExperienceEvent
.getActionDataString(
feedbackOption = selectedOption,
feedbackText = userInput
)
)
isSurveyVisible = false
endYearInReviewActivity(coroutineScope, this)
}
)
}
// TODO: implement survey in the article screen.

NavHost(
navController = navController,
startDestination = YearInReviewNavigation.Onboarding.name,
startDestination = YearInReviewNavigation.ScreenDeck.name,
enterTransition = { EnterTransition.None },
popEnterTransition = { EnterTransition.None },
popExitTransition = { ExitTransition.None },
exitTransition = { ExitTransition.None }
) {
composable(route = YearInReviewNavigation.Onboarding.name) {
YearInReviewOnboardingScreen(
contentData = YearInReviewViewModel.getStartedData,
onBackButtonClick = {
if (viewModel.canShowSurvey) {
isSurveyVisible = true
} else {
endYearInReviewActivity(coroutineScope, this@YearInReviewActivity)
}
},
onGetStartedClick = {
navController.navigate(
route = YearInReviewNavigation.ScreenDeck.name
)
}
)
}
composable(route = YearInReviewNavigation.ScreenDeck.name) {
val screenState = viewModel.uiScreenListState.collectAsState().value
YearInReviewScreenDeck(
Expand All @@ -105,11 +58,10 @@ class YearInReviewActivity : BaseActivity() {
pagerState.animateScrollToPage(pagerState.currentPage - 1)
}
} else {
navController.popBackStack()
finish()
}
},
onNextButtonClick = { pagerState ->
viewModel.canShowSurvey = true
coroutineScope.launch {
pagerState.animateScrollToPage(pagerState.currentPage + 1)
}
Expand Down Expand Up @@ -138,9 +90,5 @@ class YearInReviewActivity : BaseActivity() {
fun newIntent(context: Context): Intent {
return Intent(context, YearInReviewActivity::class.java)
}

fun endYearInReviewActivity(scope: CoroutineScope, activity: YearInReviewActivity) {
activity.finish()
}
}
}

This file was deleted.

Loading
Loading