From 5d4c14aed93fa190800b138049e81d7509bd1ed0 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Mon, 7 Aug 2023 19:30:08 +0200 Subject: [PATCH 1/5] Correct the calculations for choosing the earliest day to show in the calendar / selected day (#3923) To determine the earliest day to show in the calendar, take the current date/time, add the minimum scheduled seconds buffer (which may roll the date/time over to the next day), and then clamp to the start of that day. So it's either today (if the current time + minimum scheduled seconds is less than midnight) or it's tomorrow. When displaying the calendar work around a misfeature in Material Date Picker. It accepts UTC seconds-since-epoch, but does not convert it to the local time for display. While I'm here, show the selected day in the time picker's title. Fixes https://github.com/tuskyapp/Tusky/issues/3916 --- .../compose/view/ComposeScheduleView.kt | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.kt index f2e00d341..2f6fad162 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.kt @@ -57,7 +57,9 @@ class ComposeScheduleView ).apply { timeZone = TimeZone.getTimeZone("UTC") } - private var scheduleDateTime: Calendar? = null + + /** The date/time the user has chosen to schedule the status, in UTC */ + private var scheduleDateTimeUtc: Calendar? = null init { binding.scheduledDateTime.setOnClickListener { openPickDateDialog() } @@ -71,13 +73,13 @@ class ComposeScheduleView } private fun updateScheduleUi() { - if (scheduleDateTime == null) { + if (scheduleDateTimeUtc == null) { binding.scheduledDateTime.text = "" binding.invalidScheduleWarning.visibility = GONE return } - val scheduled = scheduleDateTime!!.time + val scheduled = scheduleDateTimeUtc!!.time binding.scheduledDateTime.text = String.format( "%s %s", dateFormat.format(scheduled), @@ -98,21 +100,37 @@ class ComposeScheduleView } fun resetSchedule() { - scheduleDateTime = null + scheduleDateTimeUtc = null updateScheduleUi() } fun openPickDateDialog() { - val yesterday = Calendar.getInstance().timeInMillis - 24 * 60 * 60 * 1000 + // The earliest point in time the calendar should display. Start with current date/time + val earliest = calendar().apply { + // Add the minimum scheduling interval. This may roll the calendar over to the + // next day (e.g. if the current time is 23:57). + add(Calendar.SECOND, MINIMUM_SCHEDULED_SECONDS) + // Clear out the time components, so it's midnight + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } val calendarConstraints = CalendarConstraints.Builder() - .setValidator( - DateValidatorPointForward.from(yesterday) - ) + .setValidator(DateValidatorPointForward.from(earliest.timeInMillis)) .build() initializeSuggestedTime() + + // Work around a misfeature in MaterialDatePicker. The `selection` is treated as + // millis-from-epoch, in UTC, which is good. However, it is also *displayed* in UTC + // instead of converting to the user's local timezone. + // + // So we have to add the TZ offset before setting it in the picker + val tzOffset = TimeZone.getDefault().getOffset(scheduleDateTimeUtc!!.timeInMillis) + val picker = MaterialDatePicker.Builder .datePicker() - .setSelection(scheduleDateTime!!.timeInMillis) + .setSelection(scheduleDateTimeUtc!!.timeInMillis + tzOffset) .setCalendarConstraints(calendarConstraints) .build() picker.addOnPositiveButtonClickListener { selection: Long -> onDateSet(selection) } @@ -129,11 +147,12 @@ class ComposeScheduleView private fun openPickTimeDialog() { val pickerBuilder = MaterialTimePicker.Builder() - scheduleDateTime?.let { + scheduleDateTimeUtc?.let { pickerBuilder.setHour(it[Calendar.HOUR_OF_DAY]) .setMinute(it[Calendar.MINUTE]) } + pickerBuilder.setTitleText(dateFormat.format(scheduleDateTimeUtc!!.timeInMillis)) pickerBuilder.setTimeFormat(getTimeFormat(context)) val picker = pickerBuilder.build() @@ -154,7 +173,7 @@ class ComposeScheduleView fun setDateTime(scheduledAt: String?) { val date = getDateTime(scheduledAt) ?: return initializeSuggestedTime() - scheduleDateTime!!.time = date + scheduleDateTimeUtc!!.time = date updateScheduleUi() } @@ -180,24 +199,24 @@ class ComposeScheduleView // see https://github.com/material-components/material-components-android/issues/882 newDate.timeZone = TimeZone.getTimeZone("UTC") newDate.timeInMillis = selection - scheduleDateTime!![newDate[Calendar.YEAR], newDate[Calendar.MONTH]] = newDate[Calendar.DATE] + scheduleDateTimeUtc!![newDate[Calendar.YEAR], newDate[Calendar.MONTH]] = newDate[Calendar.DATE] openPickTimeDialog() } private fun onTimeSet(hourOfDay: Int, minute: Int) { initializeSuggestedTime() - scheduleDateTime?.set(Calendar.HOUR_OF_DAY, hourOfDay) - scheduleDateTime?.set(Calendar.MINUTE, minute) + scheduleDateTimeUtc?.set(Calendar.HOUR_OF_DAY, hourOfDay) + scheduleDateTimeUtc?.set(Calendar.MINUTE, minute) updateScheduleUi() listener?.onTimeSet(time) } val time: String? - get() = scheduleDateTime?.time?.let { iso8601.format(it) } + get() = scheduleDateTimeUtc?.time?.let { iso8601.format(it) } private fun initializeSuggestedTime() { - if (scheduleDateTime == null) { - scheduleDateTime = calendar().apply { + if (scheduleDateTimeUtc == null) { + scheduleDateTimeUtc = calendar().apply { add(Calendar.MINUTE, 15) } } From 498c7646b03ed82db202b3149ce3fb2de64f18e5 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Mon, 7 Aug 2023 19:30:21 +0200 Subject: [PATCH 2/5] Add `newLintBaseline` task (#3925) Add a task to regenerate the lint baseline. --- app/build.gradle | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1e67b551b..3c0702d3e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,8 +60,7 @@ android { lint { lintConfig file("lint.xml") - // Regenerate by deleting app/lint-baseline.xml, then run: - // ./gradlew lintBlueDebug + // Regenerate by running `./gradlew app:newLintBaseline` baseline = file("lint-baseline.xml") } @@ -203,3 +202,18 @@ tasks.withType(org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask) { "--add-opens", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"]) } + +tasks.register("newLintBaseline") { + description 'Deletes and then recreates the lint baseline' + + // This task should always run, irrespective of caching + notCompatibleWithConfigurationCache("Is always out of date") + outputs.upToDateWhen { false } + + doLast { + delete android.lint.baseline.path + } + + // Regenerate the lint baseline + it.finalizedBy tasks.named("lintBlueDebug") +} From 791092ef136de2c4ebb44378a3f24105f13d518b Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Mon, 7 Aug 2023 19:30:56 +0200 Subject: [PATCH 3/5] Fix extra clicks on media tab (#3930) If: 1. You're viewing an account's media tab 2. Some of the media was marked sensitivei 3. The `alwaysShowSensitiveMedia` setting was `true` tapping on the image (once) would do nothing visible, because it was treated as the "reveal sensitive media" tap. You had to tap on it a second time to open it. Fix this, by passing the preference value through to the relevant code. --------- Co-authored-by: Tiga! --- .../tusky/components/account/media/AccountMediaFragment.kt | 3 --- .../components/account/media/AccountMediaGridAdapter.kt | 3 +-- .../components/account/media/AccountMediaRemoteMediator.kt | 5 +++-- .../tusky/components/account/media/AccountMediaViewModel.kt | 6 +++++- .../tusky/components/notifications/NotificationsFragment.kt | 6 +++++- .../tusky/components/viewthread/ViewThreadFragment.kt | 6 +++++- .../com/keylesspalace/tusky/viewdata/AttachmentViewData.kt | 4 ++-- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt index b39a8b5b4..5a81c418e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt @@ -82,13 +82,10 @@ class AccountMediaFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) - val alwaysShowSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia - val preferences = PreferenceManager.getDefaultSharedPreferences(view.context) val useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true) adapter = AccountMediaGridAdapter( - alwaysShowSensitiveMedia = alwaysShowSensitiveMedia, useBlurhash = useBlurhash, context = view.context, onAttachmentClickListener = ::onAttachmentClick diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt index 48b3a21da..aecbeb0bb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt @@ -24,7 +24,6 @@ import com.keylesspalace.tusky.viewdata.AttachmentViewData import java.util.Random class AccountMediaGridAdapter( - private val alwaysShowSensitiveMedia: Boolean, private val useBlurhash: Boolean, context: Context, private val onAttachmentClickListener: (AttachmentViewData, View) -> Unit @@ -80,7 +79,7 @@ class AccountMediaGridAdapter( .into(imageView) imageView.contentDescription = item.attachment.getFormattedDescription(context) - } else if (item.sensitive && !item.isRevealed && !alwaysShowSensitiveMedia) { + } else if (item.sensitive && !item.isRevealed) { overlay.show() overlay.setImageDrawable(mediaHiddenDrawable) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt index 315b0380c..52535a6a5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt @@ -20,6 +20,7 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import com.keylesspalace.tusky.components.timeline.util.ifExpected +import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.viewdata.AttachmentViewData import retrofit2.HttpException @@ -27,9 +28,9 @@ import retrofit2.HttpException @OptIn(ExperimentalPagingApi::class) class AccountMediaRemoteMediator( private val api: MastodonApi, + private val activeAccount: AccountEntity, private val viewModel: AccountMediaViewModel ) : RemoteMediator() { - override suspend fun load( loadType: LoadType, state: PagingState @@ -58,7 +59,7 @@ class AccountMediaRemoteMediator( } val attachments = statuses.flatMap { status -> - AttachmentViewData.list(status) + AttachmentViewData.list(status, activeAccount.alwaysShowSensitiveMedia ?: false) } if (loadType == LoadType.REFRESH) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaViewModel.kt index ddbcb71d0..ee5ffd011 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaViewModel.kt @@ -21,11 +21,13 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn +import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.viewdata.AttachmentViewData import javax.inject.Inject class AccountMediaViewModel @Inject constructor( + private val accountManager: AccountManager, api: MastodonApi ) : ViewModel() { @@ -35,6 +37,8 @@ class AccountMediaViewModel @Inject constructor( var currentSource: AccountMediaPagingSource? = null + val activeAccount = accountManager.activeAccount!! + @OptIn(ExperimentalPagingApi::class) val media = Pager( config = PagingConfig( @@ -48,7 +52,7 @@ class AccountMediaViewModel @Inject constructor( currentSource = source } }, - remoteMediator = AccountMediaRemoteMediator(api, this) + remoteMediator = AccountMediaRemoteMediator(api, activeAccount, this) ).flow .cachedIn(viewModelScope) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt index a80fd96aa..f26a46528 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt @@ -518,7 +518,11 @@ class NotificationsFragment : override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) { val status = adapter.peek(position)?.statusViewData?.status ?: return - super.viewMedia(attachmentIndex, list(status), view) + super.viewMedia( + attachmentIndex, + list(status, viewModel.statusDisplayOptions.value.showSensitiveMedia), + view + ) } override fun onViewThread(position: Int) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt index 0d4ba3b35..d7541f428 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt @@ -336,7 +336,11 @@ class ViewThreadFragment : override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) { val status = adapter.currentList[position].status - super.viewMedia(attachmentIndex, list(status), view) + super.viewMedia( + attachmentIndex, + list(status, alwaysShowSensitiveMedia), + view + ) } override fun onViewThread(position: Int) { diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt index ae24cebe1..998cc96a4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt @@ -35,7 +35,7 @@ data class AttachmentViewData( companion object { @JvmStatic - fun list(status: Status): List { + fun list(status: Status, alwaysShowSensitiveMedia: Boolean = false): List { val actionable = status.actionableStatus return actionable.attachments.map { attachment -> AttachmentViewData( @@ -43,7 +43,7 @@ data class AttachmentViewData( statusId = actionable.id, statusUrl = actionable.url!!, sensitive = actionable.sensitive, - isRevealed = !actionable.sensitive + isRevealed = alwaysShowSensitiveMedia || !actionable.sensitive ) } } From b88f1d810b5adc75698ed9697e863420448ba56d Mon Sep 17 00:00:00 2001 From: Angelo Suzuki <1063155+tinsukE@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:32:17 +0200 Subject: [PATCH 4/5] Make CW description selectable (#3926) While the status text in the detailed layout was selectable, CW description wasn't. Fixes #3826. --- app/src/main/res/layout/item_status_detailed.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/item_status_detailed.xml b/app/src/main/res/layout/item_status_detailed.xml index cc6e4de2e..b9f64bd3b 100644 --- a/app/src/main/res/layout/item_status_detailed.xml +++ b/app/src/main/res/layout/item_status_detailed.xml @@ -89,6 +89,7 @@ android:importantForAccessibility="no" android:lineSpacingMultiplier="1.1" android:textColor="?android:textColorPrimary" + android:textIsSelectable="true" android:textSize="?attr/status_text_large" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From 468de28ac6c5453b6a5408b3e2d5a08b5191c02f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 01:56:00 +0200 Subject: [PATCH 5/5] Update plugin ktlint to v11.5.1 (#3941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | org.jlleitschuh.gradle.ktlint | `11.5.0` -> `11.5.1` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.jlleitschuh.gradle.ktlint/11.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jlleitschuh.gradle.ktlint/11.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jlleitschuh.gradle.ktlint/11.5.0/11.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jlleitschuh.gradle.ktlint/11.5.0/11.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/tuskyapp/Tusky). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e90e5c72b..447d295b7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,7 +60,7 @@ google-ksp = "com.google.devtools.ksp:1.9.0-1.0.13" kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } -ktlint = "org.jlleitschuh.gradle.ktlint:11.5.0" +ktlint = "org.jlleitschuh.gradle.ktlint:11.5.1" [libraries] android-material = { module = "com.google.android.material:material", version.ref = "material" }