feat: add NIP-40 expiration date to short note post screen
Allows users to add an expiration date to posts via NIP-40. The feature follows the same pattern as content warning (toggle button in the bottom action bar, expandable section in the post body) and uses the same calendar/time picker UI as poll deadlines. - ExpirationDateButton: timer icon toggles orange when active - ExpirationDatePicker: date+time picker with "Expires in X" display - ViewModel: wantsExpirationDate + expirationDate state, wired into both TextNoteEvent and PollEvent builders, loaded from drafts - Strings: add_expiration_date, expiration_date_label, etc. https://claude.ai/code/session_016QjmArr6zfM1Qx58iNrntA
This commit is contained in:
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.note.creators.expiration
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Timer
|
||||||
|
import androidx.compose.material.icons.outlined.TimerOff
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExpirationDateButton(
|
||||||
|
isActive: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onClick() },
|
||||||
|
) {
|
||||||
|
if (!isActive) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Timer,
|
||||||
|
contentDescription = stringRes(R.string.add_expiration_date),
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.TimerOff,
|
||||||
|
contentDescription = stringRes(R.string.remove_expiration_date),
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = Color(0xFFFF6600),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.note.creators.expiration
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Timer
|
||||||
|
import androidx.compose.material3.DatePicker
|
||||||
|
import androidx.compose.material3.DatePickerDialog
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.SelectableDates
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TimePicker
|
||||||
|
import androidx.compose.material3.TimePickerDialog
|
||||||
|
import androidx.compose.material3.rememberDatePickerState
|
||||||
|
import androidx.compose.material3.rememberTimePickerState
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.timeAheadNoDot
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ExpirationDatePicker(model: ShortNotePostViewModel) {
|
||||||
|
var showDatePicker by remember { mutableStateOf(false) }
|
||||||
|
var showTimePicker by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val currentTime = Instant.ofEpochMilli(model.expirationDate * 1000).atZone(ZoneId.systemDefault()).toLocalDateTime()
|
||||||
|
|
||||||
|
val datePickerState =
|
||||||
|
rememberDatePickerState(
|
||||||
|
initialSelectedDateMillis = model.expirationDate * 1000,
|
||||||
|
yearRange = currentTime.year..2050,
|
||||||
|
selectableDates =
|
||||||
|
object : SelectableDates {
|
||||||
|
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
||||||
|
return utcTimeMillis >= System.currentTimeMillis() - 86400000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val timePickerState =
|
||||||
|
rememberTimePickerState(
|
||||||
|
initialHour = currentTime.hour,
|
||||||
|
initialMinute = currentTime.minute,
|
||||||
|
is24Hour = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 10.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Timer,
|
||||||
|
contentDescription = stringRes(R.string.expiration_date_label),
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = Color(0xFFFF6600),
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.expiration_date_label),
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.W500,
|
||||||
|
modifier = Modifier.padding(start = 10.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.expiration_date_explainer),
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
modifier = Modifier.padding(vertical = 10.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedCard(
|
||||||
|
onClick = { showDatePicker = true },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(Icons.Outlined.Timer, contentDescription = stringResource(R.string.expiration_date_select))
|
||||||
|
Spacer(Modifier.width(12.dp))
|
||||||
|
|
||||||
|
if (model.expirationDate < TimeUtils.oneMinuteFromNow()) {
|
||||||
|
Text(stringRes(R.string.expiration_date_label) + " " + model.expirationDate, style = MaterialTheme.typography.bodyLarge)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.expiration_expires_in, timeAheadNoDot(model.expirationDate, context)),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDatePicker) {
|
||||||
|
DatePickerDialog(
|
||||||
|
onDismissRequest = { showDatePicker = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
showDatePicker = false
|
||||||
|
showTimePicker = true
|
||||||
|
}) { Text(stringResource(R.string.next)) }
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
DatePicker(state = datePickerState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showTimePicker) {
|
||||||
|
TimePickerDialog(
|
||||||
|
title = {
|
||||||
|
Text(stringResource(R.string.expiration_time))
|
||||||
|
},
|
||||||
|
onDismissRequest = { showTimePicker = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
val datetimeLocalTimeZone =
|
||||||
|
datePickerState.selectedDateMillis?.let { localDayAtZeroHourMillis ->
|
||||||
|
(localDayAtZeroHourMillis / 1000) +
|
||||||
|
(timePickerState.hour * TimeUtils.ONE_HOUR) +
|
||||||
|
(timePickerState.minute * TimeUtils.ONE_MINUTE)
|
||||||
|
} ?: TimeUtils.oneDayAhead()
|
||||||
|
|
||||||
|
val offset: ZoneOffset = ZoneId.systemDefault().rules.getOffset(Instant.now())
|
||||||
|
|
||||||
|
model.expirationDate = datetimeLocalTimeZone - offset.totalSeconds
|
||||||
|
|
||||||
|
showTimePicker = false
|
||||||
|
},
|
||||||
|
) { Text(stringResource(R.string.confirm)) }
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
TimePicker(state = timePickerState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -78,6 +78,8 @@ import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
|||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer
|
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton
|
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDateButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePicker
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.ShowEmojiSuggestionList
|
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.ShowEmojiSuggestionList
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList
|
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.AddLnInvoiceButton
|
import com.vitorpamplona.amethyst.ui.note.creators.invoice.AddLnInvoiceButton
|
||||||
@@ -311,6 +313,15 @@ private fun NewPostScreenBody(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (postViewModel.wantsExpirationDate) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = CenterVertically,
|
||||||
|
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||||
|
) {
|
||||||
|
ExpirationDatePicker(postViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (postViewModel.wantsToAddGeoHash) {
|
if (postViewModel.wantsToAddGeoHash) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = CenterVertically,
|
verticalAlignment = CenterVertically,
|
||||||
@@ -539,6 +550,10 @@ private fun BottomRowActions(postViewModel: ShortNotePostViewModel) {
|
|||||||
postViewModel.toggleMarkAsSensitive()
|
postViewModel.toggleMarkAsSensitive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpirationDateButton(postViewModel.wantsExpirationDate) {
|
||||||
|
postViewModel.toggleExpirationDate()
|
||||||
|
}
|
||||||
|
|
||||||
AddGeoHashButton(postViewModel.wantsToAddGeoHash) {
|
AddGeoHashButton(postViewModel.wantsToAddGeoHash) {
|
||||||
postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash
|
postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -97,6 +97,7 @@ import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning
|
|||||||
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
||||||
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitive
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitive
|
||||||
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||||
|
import com.vitorpamplona.quartz.nip40Expiration.expiration
|
||||||
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
|
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
|
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetupLnAddress
|
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetupLnAddress
|
||||||
@@ -239,6 +240,10 @@ open class ShortNotePostViewModel :
|
|||||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||||
var contentWarningDescription by mutableStateOf("")
|
var contentWarningDescription by mutableStateOf("")
|
||||||
|
|
||||||
|
// Expiration Date (NIP-40)
|
||||||
|
var wantsExpirationDate by mutableStateOf(false)
|
||||||
|
var expirationDate by mutableLongStateOf(TimeUtils.oneDayAhead())
|
||||||
|
|
||||||
// GeoHash
|
// GeoHash
|
||||||
var wantsToAddGeoHash by mutableStateOf(false)
|
var wantsToAddGeoHash by mutableStateOf(false)
|
||||||
var location: StateFlow<LocationState.LocationResult>? = null
|
var location: StateFlow<LocationState.LocationResult>? = null
|
||||||
@@ -427,6 +432,10 @@ open class ShortNotePostViewModel :
|
|||||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||||
contentWarningDescription = draftEvent.contentWarningReason() ?: ""
|
contentWarningDescription = draftEvent.contentWarningReason() ?: ""
|
||||||
|
|
||||||
|
val draftExpiration = draftEvent.tags.expiration()
|
||||||
|
wantsExpirationDate = draftExpiration != null
|
||||||
|
expirationDate = draftExpiration ?: TimeUtils.oneDayAhead()
|
||||||
|
|
||||||
val geohash = draftEvent.getGeoHash()
|
val geohash = draftEvent.getGeoHash()
|
||||||
wantsToAddGeoHash = geohash != null
|
wantsToAddGeoHash = geohash != null
|
||||||
if (geohash != null) {
|
if (geohash != null) {
|
||||||
@@ -507,6 +516,10 @@ open class ShortNotePostViewModel :
|
|||||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||||
contentWarningDescription = draftEvent.contentWarningReason() ?: ""
|
contentWarningDescription = draftEvent.contentWarningReason() ?: ""
|
||||||
|
|
||||||
|
val draftExpiration = draftEvent.tags.expiration()
|
||||||
|
wantsExpirationDate = draftExpiration != null
|
||||||
|
expirationDate = draftExpiration ?: TimeUtils.oneDayAhead()
|
||||||
|
|
||||||
val geohash = draftEvent.getGeoHash()
|
val geohash = draftEvent.getGeoHash()
|
||||||
wantsToAddGeoHash = geohash != null
|
wantsToAddGeoHash = geohash != null
|
||||||
if (geohash != null) {
|
if (geohash != null) {
|
||||||
@@ -690,6 +703,7 @@ open class ShortNotePostViewModel :
|
|||||||
val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet())
|
val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet())
|
||||||
|
|
||||||
val contentWarningReason = if (wantsToMarkAsSensitive) contentWarningDescription else null
|
val contentWarningReason = if (wantsToMarkAsSensitive) contentWarningDescription else null
|
||||||
|
val localExpirationDate = if (wantsExpirationDate) expirationDate else null
|
||||||
|
|
||||||
return if (wantsPoll) {
|
return if (wantsPoll) {
|
||||||
val options = pollOptions.map { it.value }
|
val options = pollOptions.map { it.value }
|
||||||
@@ -710,6 +724,7 @@ open class ShortNotePostViewModel :
|
|||||||
localZapRaiserAmount?.let { zapraiser(it) }
|
localZapRaiserAmount?.let { zapraiser(it) }
|
||||||
zapReceiver?.let { zapSplits(it) }
|
zapReceiver?.let { zapSplits(it) }
|
||||||
contentWarningReason?.let { contentWarning(it) }
|
contentWarningReason?.let { contentWarning(it) }
|
||||||
|
localExpirationDate?.let { expiration(it) }
|
||||||
|
|
||||||
emojis(emojis)
|
emojis(emojis)
|
||||||
imetas(usedAttachments)
|
imetas(usedAttachments)
|
||||||
@@ -765,6 +780,7 @@ open class ShortNotePostViewModel :
|
|||||||
localZapRaiserAmount?.let { zapraiser(it) }
|
localZapRaiserAmount?.let { zapraiser(it) }
|
||||||
zapReceiver?.let { zapSplits(it) }
|
zapReceiver?.let { zapSplits(it) }
|
||||||
contentWarningReason?.let { contentWarning(it) }
|
contentWarningReason?.let { contentWarning(it) }
|
||||||
|
localExpirationDate?.let { expiration(it) }
|
||||||
|
|
||||||
emojis(emojis)
|
emojis(emojis)
|
||||||
imetas(usedAttachments)
|
imetas(usedAttachments)
|
||||||
@@ -1237,5 +1253,13 @@ open class ShortNotePostViewModel :
|
|||||||
draftTag.newVersion()
|
draftTag.newVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleExpirationDate() {
|
||||||
|
wantsExpirationDate = !wantsExpirationDate
|
||||||
|
if (wantsExpirationDate) {
|
||||||
|
expirationDate = TimeUtils.oneDayAhead()
|
||||||
|
}
|
||||||
|
draftTag.newVersion()
|
||||||
|
}
|
||||||
|
|
||||||
override fun locationManager(): LocationState = Amethyst.instance.locationManager
|
override fun locationManager(): LocationState = Amethyst.instance.locationManager
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1291,6 +1291,14 @@
|
|||||||
|
|
||||||
<string name="add_content_warning">Add content warning</string>
|
<string name="add_content_warning">Add content warning</string>
|
||||||
<string name="remove_content_warning">Remove content warning</string>
|
<string name="remove_content_warning">Remove content warning</string>
|
||||||
|
|
||||||
|
<string name="add_expiration_date">Add expiration date</string>
|
||||||
|
<string name="remove_expiration_date">Remove expiration date</string>
|
||||||
|
<string name="expiration_date_label">Expiration Date</string>
|
||||||
|
<string name="expiration_date_explainer">Post will be hidden by clients after this date (NIP-40)</string>
|
||||||
|
<string name="expiration_date_select">Select expiration date and time</string>
|
||||||
|
<string name="expiration_expires_in">Expires in %1$s</string>
|
||||||
|
<string name="expiration_time">Expiration Time</string>
|
||||||
<string name="show_npub_as_a_qr_code">Show npub as a QR code</string>
|
<string name="show_npub_as_a_qr_code">Show npub as a QR code</string>
|
||||||
<string name="show_nprofile_as_a_qr_code">Show nprofile as a QR code</string>
|
<string name="show_nprofile_as_a_qr_code">Show nprofile as a QR code</string>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user