Merge branch 'tuskyapp:develop' into bookwyrm-urls

This commit is contained in:
Mike Barnes 2023-08-08 20:05:04 +10:00 committed by GitHub
commit 5596e31769
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 75 additions and 32 deletions

View File

@ -60,8 +60,7 @@ android {
lint { lint {
lintConfig file("lint.xml") lintConfig file("lint.xml")
// Regenerate by deleting app/lint-baseline.xml, then run: // Regenerate by running `./gradlew app:newLintBaseline`
// ./gradlew lintBlueDebug
baseline = file("lint-baseline.xml") 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.parser=ALL-UNNAMED",
"--add-opens", "jdk.compiler/com.sun.tools.javac.code=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")
}

View File

@ -82,13 +82,10 @@ class AccountMediaFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
val alwaysShowSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia
val preferences = PreferenceManager.getDefaultSharedPreferences(view.context) val preferences = PreferenceManager.getDefaultSharedPreferences(view.context)
val useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true) val useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true)
adapter = AccountMediaGridAdapter( adapter = AccountMediaGridAdapter(
alwaysShowSensitiveMedia = alwaysShowSensitiveMedia,
useBlurhash = useBlurhash, useBlurhash = useBlurhash,
context = view.context, context = view.context,
onAttachmentClickListener = ::onAttachmentClick onAttachmentClickListener = ::onAttachmentClick

View File

@ -24,7 +24,6 @@ import com.keylesspalace.tusky.viewdata.AttachmentViewData
import java.util.Random import java.util.Random
class AccountMediaGridAdapter( class AccountMediaGridAdapter(
private val alwaysShowSensitiveMedia: Boolean,
private val useBlurhash: Boolean, private val useBlurhash: Boolean,
context: Context, context: Context,
private val onAttachmentClickListener: (AttachmentViewData, View) -> Unit private val onAttachmentClickListener: (AttachmentViewData, View) -> Unit
@ -80,7 +79,7 @@ class AccountMediaGridAdapter(
.into(imageView) .into(imageView)
imageView.contentDescription = item.attachment.getFormattedDescription(context) imageView.contentDescription = item.attachment.getFormattedDescription(context)
} else if (item.sensitive && !item.isRevealed && !alwaysShowSensitiveMedia) { } else if (item.sensitive && !item.isRevealed) {
overlay.show() overlay.show()
overlay.setImageDrawable(mediaHiddenDrawable) overlay.setImageDrawable(mediaHiddenDrawable)

View File

@ -20,6 +20,7 @@ import androidx.paging.LoadType
import androidx.paging.PagingState import androidx.paging.PagingState
import androidx.paging.RemoteMediator import androidx.paging.RemoteMediator
import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.components.timeline.util.ifExpected
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.AttachmentViewData
import retrofit2.HttpException import retrofit2.HttpException
@ -27,9 +28,9 @@ import retrofit2.HttpException
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
class AccountMediaRemoteMediator( class AccountMediaRemoteMediator(
private val api: MastodonApi, private val api: MastodonApi,
private val activeAccount: AccountEntity,
private val viewModel: AccountMediaViewModel private val viewModel: AccountMediaViewModel
) : RemoteMediator<String, AttachmentViewData>() { ) : RemoteMediator<String, AttachmentViewData>() {
override suspend fun load( override suspend fun load(
loadType: LoadType, loadType: LoadType,
state: PagingState<String, AttachmentViewData> state: PagingState<String, AttachmentViewData>
@ -58,7 +59,7 @@ class AccountMediaRemoteMediator(
} }
val attachments = statuses.flatMap { status -> val attachments = statuses.flatMap { status ->
AttachmentViewData.list(status) AttachmentViewData.list(status, activeAccount.alwaysShowSensitiveMedia ?: false)
} }
if (loadType == LoadType.REFRESH) { if (loadType == LoadType.REFRESH) {

View File

@ -21,11 +21,13 @@ import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.AttachmentViewData
import javax.inject.Inject import javax.inject.Inject
class AccountMediaViewModel @Inject constructor( class AccountMediaViewModel @Inject constructor(
private val accountManager: AccountManager,
api: MastodonApi api: MastodonApi
) : ViewModel() { ) : ViewModel() {
@ -35,6 +37,8 @@ class AccountMediaViewModel @Inject constructor(
var currentSource: AccountMediaPagingSource? = null var currentSource: AccountMediaPagingSource? = null
val activeAccount = accountManager.activeAccount!!
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
val media = Pager( val media = Pager(
config = PagingConfig( config = PagingConfig(
@ -48,7 +52,7 @@ class AccountMediaViewModel @Inject constructor(
currentSource = source currentSource = source
} }
}, },
remoteMediator = AccountMediaRemoteMediator(api, this) remoteMediator = AccountMediaRemoteMediator(api, activeAccount, this)
).flow ).flow
.cachedIn(viewModelScope) .cachedIn(viewModelScope)

View File

@ -57,7 +57,9 @@ class ComposeScheduleView
).apply { ).apply {
timeZone = TimeZone.getTimeZone("UTC") 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 { init {
binding.scheduledDateTime.setOnClickListener { openPickDateDialog() } binding.scheduledDateTime.setOnClickListener { openPickDateDialog() }
@ -71,13 +73,13 @@ class ComposeScheduleView
} }
private fun updateScheduleUi() { private fun updateScheduleUi() {
if (scheduleDateTime == null) { if (scheduleDateTimeUtc == null) {
binding.scheduledDateTime.text = "" binding.scheduledDateTime.text = ""
binding.invalidScheduleWarning.visibility = GONE binding.invalidScheduleWarning.visibility = GONE
return return
} }
val scheduled = scheduleDateTime!!.time val scheduled = scheduleDateTimeUtc!!.time
binding.scheduledDateTime.text = String.format( binding.scheduledDateTime.text = String.format(
"%s %s", "%s %s",
dateFormat.format(scheduled), dateFormat.format(scheduled),
@ -98,21 +100,37 @@ class ComposeScheduleView
} }
fun resetSchedule() { fun resetSchedule() {
scheduleDateTime = null scheduleDateTimeUtc = null
updateScheduleUi() updateScheduleUi()
} }
fun openPickDateDialog() { 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() val calendarConstraints = CalendarConstraints.Builder()
.setValidator( .setValidator(DateValidatorPointForward.from(earliest.timeInMillis))
DateValidatorPointForward.from(yesterday)
)
.build() .build()
initializeSuggestedTime() 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 val picker = MaterialDatePicker.Builder
.datePicker() .datePicker()
.setSelection(scheduleDateTime!!.timeInMillis) .setSelection(scheduleDateTimeUtc!!.timeInMillis + tzOffset)
.setCalendarConstraints(calendarConstraints) .setCalendarConstraints(calendarConstraints)
.build() .build()
picker.addOnPositiveButtonClickListener { selection: Long -> onDateSet(selection) } picker.addOnPositiveButtonClickListener { selection: Long -> onDateSet(selection) }
@ -129,11 +147,12 @@ class ComposeScheduleView
private fun openPickTimeDialog() { private fun openPickTimeDialog() {
val pickerBuilder = MaterialTimePicker.Builder() val pickerBuilder = MaterialTimePicker.Builder()
scheduleDateTime?.let { scheduleDateTimeUtc?.let {
pickerBuilder.setHour(it[Calendar.HOUR_OF_DAY]) pickerBuilder.setHour(it[Calendar.HOUR_OF_DAY])
.setMinute(it[Calendar.MINUTE]) .setMinute(it[Calendar.MINUTE])
} }
pickerBuilder.setTitleText(dateFormat.format(scheduleDateTimeUtc!!.timeInMillis))
pickerBuilder.setTimeFormat(getTimeFormat(context)) pickerBuilder.setTimeFormat(getTimeFormat(context))
val picker = pickerBuilder.build() val picker = pickerBuilder.build()
@ -154,7 +173,7 @@ class ComposeScheduleView
fun setDateTime(scheduledAt: String?) { fun setDateTime(scheduledAt: String?) {
val date = getDateTime(scheduledAt) ?: return val date = getDateTime(scheduledAt) ?: return
initializeSuggestedTime() initializeSuggestedTime()
scheduleDateTime!!.time = date scheduleDateTimeUtc!!.time = date
updateScheduleUi() updateScheduleUi()
} }
@ -180,24 +199,24 @@ class ComposeScheduleView
// see https://github.com/material-components/material-components-android/issues/882 // see https://github.com/material-components/material-components-android/issues/882
newDate.timeZone = TimeZone.getTimeZone("UTC") newDate.timeZone = TimeZone.getTimeZone("UTC")
newDate.timeInMillis = selection newDate.timeInMillis = selection
scheduleDateTime!![newDate[Calendar.YEAR], newDate[Calendar.MONTH]] = newDate[Calendar.DATE] scheduleDateTimeUtc!![newDate[Calendar.YEAR], newDate[Calendar.MONTH]] = newDate[Calendar.DATE]
openPickTimeDialog() openPickTimeDialog()
} }
private fun onTimeSet(hourOfDay: Int, minute: Int) { private fun onTimeSet(hourOfDay: Int, minute: Int) {
initializeSuggestedTime() initializeSuggestedTime()
scheduleDateTime?.set(Calendar.HOUR_OF_DAY, hourOfDay) scheduleDateTimeUtc?.set(Calendar.HOUR_OF_DAY, hourOfDay)
scheduleDateTime?.set(Calendar.MINUTE, minute) scheduleDateTimeUtc?.set(Calendar.MINUTE, minute)
updateScheduleUi() updateScheduleUi()
listener?.onTimeSet(time) listener?.onTimeSet(time)
} }
val time: String? val time: String?
get() = scheduleDateTime?.time?.let { iso8601.format(it) } get() = scheduleDateTimeUtc?.time?.let { iso8601.format(it) }
private fun initializeSuggestedTime() { private fun initializeSuggestedTime() {
if (scheduleDateTime == null) { if (scheduleDateTimeUtc == null) {
scheduleDateTime = calendar().apply { scheduleDateTimeUtc = calendar().apply {
add(Calendar.MINUTE, 15) add(Calendar.MINUTE, 15)
} }
} }

View File

@ -518,7 +518,11 @@ class NotificationsFragment :
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) { override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
val status = adapter.peek(position)?.statusViewData?.status ?: return 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) { override fun onViewThread(position: Int) {

View File

@ -336,7 +336,11 @@ class ViewThreadFragment :
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) { override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
val status = adapter.currentList[position].status val status = adapter.currentList[position].status
super.viewMedia(attachmentIndex, list(status), view) super.viewMedia(
attachmentIndex,
list(status, alwaysShowSensitiveMedia),
view
)
} }
override fun onViewThread(position: Int) { override fun onViewThread(position: Int) {

View File

@ -35,7 +35,7 @@ data class AttachmentViewData(
companion object { companion object {
@JvmStatic @JvmStatic
fun list(status: Status): List<AttachmentViewData> { fun list(status: Status, alwaysShowSensitiveMedia: Boolean = false): List<AttachmentViewData> {
val actionable = status.actionableStatus val actionable = status.actionableStatus
return actionable.attachments.map { attachment -> return actionable.attachments.map { attachment ->
AttachmentViewData( AttachmentViewData(
@ -43,7 +43,7 @@ data class AttachmentViewData(
statusId = actionable.id, statusId = actionable.id,
statusUrl = actionable.url!!, statusUrl = actionable.url!!,
sensitive = actionable.sensitive, sensitive = actionable.sensitive,
isRevealed = !actionable.sensitive isRevealed = alwaysShowSensitiveMedia || !actionable.sensitive
) )
} }
} }

View File

@ -89,6 +89,7 @@
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textIsSelectable="true"
android:textSize="?attr/status_text_large" android:textSize="?attr/status_text_large"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -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-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", 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] [libraries]
android-material = { module = "com.google.android.material:material", version.ref = "material" } android-material = { module = "com.google.android.material:material", version.ref = "material" }