diff --git a/app/src/main/java/org/permanent/permanent/models/Record.kt b/app/src/main/java/org/permanent/permanent/models/Record.kt index 4ca1c10f..f013a923 100644 --- a/app/src/main/java/org/permanent/permanent/models/Record.kt +++ b/app/src/main/java/org/permanent/permanent/models/Record.kt @@ -74,7 +74,7 @@ open class Record : Parcelable { folderLinkId = recordInfo.folder_linkId parentFolderLinkId = recordInfo.parentFolder_linkId displayName = recordInfo.displayName - displayDate = recordInfo.displayDT?.substringBefore("T") + displayDate = recordInfo.displayDT showArchiveThumb = false thumbURL200 = recordInfo.thumbURL200 thumbURL2000 = recordInfo.thumbURL2000 @@ -100,7 +100,7 @@ open class Record : Parcelable { folderLinkId = recordInfo.folder_linkId parentFolderLinkId = recordInfo.parentFolder_linkId displayName = recordInfo.displayName - displayDate = recordInfo.displayDT?.substringBefore("T") + displayDate = recordInfo.displayDT showArchiveThumb = false thumbURL200 = recordInfo.thumbURL200 thumbURL2000 = recordInfo.thumbURL2000 @@ -127,7 +127,7 @@ open class Record : Parcelable { folderLinkId = itemVO.folder_linkId parentFolderLinkId = itemVO.parentFolder_linkId displayName = itemVO.displayName - displayDate = itemVO.displayDT?.substringBefore("T") + displayDate = itemVO.displayDT archiveFullName = "The ${archiveVO.fullName} Archive" archiveThumbURL200 = archiveVO.thumbURL200 showArchiveThumb = showArchiveThumbnail @@ -172,7 +172,7 @@ open class Record : Parcelable { folderLinkId = recordInfo?.folder_linkId parentFolderLinkId = recordInfo?.parentFolder_linkId displayName = recordInfo?.displayName - displayDate = recordInfo?.displayDT?.substringBefore("T") + displayDate = recordInfo?.displayDT thumbURL200 = recordInfo?.thumbURL200 thumbURL2000 = recordInfo?.thumbURL2000 isThumbBlurred = shareByUrlVO.previewToggle == null || shareByUrlVO.previewToggle == 0 diff --git a/app/src/main/java/org/permanent/permanent/network/RequestContainer.kt b/app/src/main/java/org/permanent/permanent/network/RequestContainer.kt index cd76deab..0a3e8745 100644 --- a/app/src/main/java/org/permanent/permanent/network/RequestContainer.kt +++ b/app/src/main/java/org/permanent/permanent/network/RequestContainer.kt @@ -286,6 +286,7 @@ class RequestContainer { recordVO.folder_linkId = record.folderLinkId recordVO.parentFolder_linkId = record.parentFolderLinkId recordVO.displayName = record.displayName + recordVO.displayDT = record.displayDate if(locnVO != null) { recordVO.LocnVO = locnVO } diff --git a/app/src/main/java/org/permanent/permanent/network/models/FileData.kt b/app/src/main/java/org/permanent/permanent/network/models/FileData.kt index ad25a22c..24d2707a 100644 --- a/app/src/main/java/org/permanent/permanent/network/models/FileData.kt +++ b/app/src/main/java/org/permanent/permanent/network/models/FileData.kt @@ -52,7 +52,7 @@ class FileData private constructor() : Parcelable { } displayName = recordVO.displayName description = recordVO.description - displayDate = recordVO.displayDT?.substringBefore("T") + displayDate = recordVO.displayDT createdDate = recordVO.createdDT?.replace("T", " ") updatedDate = recordVO.updatedDT?.replace("T", " ") derivedDate = recordVO.derivedDT?.replace("T", " ") diff --git a/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/EditDateTimeFragment.kt b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/EditDateTimeFragment.kt new file mode 100644 index 00000000..d138310c --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/EditDateTimeFragment.kt @@ -0,0 +1,92 @@ +package org.permanent.permanent.ui.bulkEditMetadata + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.material3.MaterialTheme +import androidx.compose.ui.platform.ComposeView +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import com.google.android.libraries.places.api.Places +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import org.permanent.permanent.BuildConfig +import org.permanent.permanent.models.Record +import org.permanent.permanent.ui.PermanentBottomSheetFragment +import org.permanent.permanent.ui.bulkEditMetadata.compose.EditDateTimeScreen +import org.permanent.permanent.ui.myFiles.PARCELABLE_FILES_KEY +import org.permanent.permanent.viewmodels.EditDateTimeViewModel + +class EditDateTimeFragment : PermanentBottomSheetFragment() { + private lateinit var viewModel: EditDateTimeViewModel + private val onDateChanged = MutableLiveData() + + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + Places.initialize(requireContext(), BuildConfig.GMP_KEY) + viewModel = ViewModelProvider(this)[EditDateTimeViewModel::class.java] + + arguments?.getParcelableArrayList(PARCELABLE_FILES_KEY)?.let { + viewModel.setRecords(it) + } + + return ComposeView(requireContext()).apply { + setContent { + MaterialTheme { + EditDateTimeScreen(viewModel = viewModel, cancel = { + this@EditDateTimeFragment.dismiss() + }) + } + } + } + } + + fun setBundleArguments(records: ArrayList) { + val bundle = Bundle() + bundle.putParcelableArrayList(PARCELABLE_FILES_KEY, records) + this.arguments = bundle + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val bottomSheetDialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog + bottomSheetDialog.setOnShowListener { dialog: DialogInterface -> + val sheetDialog = dialog as BottomSheetDialog + val bottomSheet = + sheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) + BottomSheetBehavior.from(bottomSheet as FrameLayout) + .setState(BottomSheetBehavior.STATE_EXPANDED) + } + return bottomSheetDialog + } + + private val onDateChangedObserver = Observer { + onDateChanged.value = it + } + + override fun connectViewModelEvents() { + viewModel.getOnDateChanged().observe(this, onDateChangedObserver) + } + + override fun disconnectViewModelEvents() { + viewModel.getOnDateChanged().removeObserver(onDateChangedObserver) + } + + override fun onResume() { + super.onResume() + connectViewModelEvents() + } + + override fun onPause() { + super.onPause() + disconnectViewModelEvents() + } + + fun getOnDateChanged() = onDateChanged +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/EditMetadataFragment.kt b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/EditMetadataFragment.kt index 713dd7fe..41cf9eb8 100644 --- a/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/EditMetadataFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/EditMetadataFragment.kt @@ -20,6 +20,7 @@ class EditMetadataFragment : PermanentBaseFragment() { private lateinit var viewModel: EditMetadataViewModel private var newTagFragment: NewTagFragment? = null private var locationFragment: EditLocationFragment? = null + private var dateFragment: EditDateTimeFragment? = null private var records = ArrayList() override fun onCreateView( @@ -53,7 +54,11 @@ class EditMetadataFragment : PermanentBaseFragment() { fragment?.show(parentFragmentManager, fragment?.tag) }, openDateAndTimeScreen = { - + dateFragment = EditDateTimeFragment() + dateFragment?.setBundleArguments(records) + dateFragment?.show(parentFragmentManager, dateFragment?.tag) + dateFragment?.getOnDateChanged() + ?.observe(lifecycleOwner, onDateChangedObserver) }, openLocationScreen = { locationFragment = EditLocationFragment() @@ -76,6 +81,11 @@ class EditMetadataFragment : PermanentBaseFragment() { viewModel.onLocationChanged(it) } + private val onDateChangedObserver = Observer { + viewModel.onDateChanged(it) + } + + override fun connectViewModelEvents() { } @@ -83,6 +93,7 @@ class EditMetadataFragment : PermanentBaseFragment() { override fun disconnectViewModelEvents() { newTagFragment?.getOnTagsAddedToSelection()?.removeObserver(onTagsAddedToSelectionObserver) locationFragment?.getOnLocationChanged()?.removeObserver(onLocationChangedObserver) + dateFragment?.getOnDateChanged()?.removeObserver(onDateChangedObserver) } override fun onResume() { diff --git a/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/compose/EditDateTimeScreen.kt b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/compose/EditDateTimeScreen.kt new file mode 100644 index 00000000..8b486363 --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/compose/EditDateTimeScreen.kt @@ -0,0 +1,267 @@ +package org.permanent.permanent.ui.bulkEditMetadata.compose + +import CustomDialog +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.shape.RoundedCornerShape +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TimePicker +import androidx.compose.material3.TimePickerDefaults +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.material3.rememberTimePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat +import org.permanent.permanent.R +import org.permanent.permanent.viewmodels.EditDateTimeViewModel +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EditDateTimeScreen( + viewModel: EditDateTimeViewModel, + cancel: () -> Unit) { + + val superLightBlue = colorResource(id = R.color.superLightBlue) + val blue900 = colorResource(R.color.blue900) + val regularFont = FontFamily(Font(R.font.open_sans_regular_ttf)) + + val openAlertDialog = remember { mutableStateOf(false) } + + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = viewModel.initialDateMilis + ) + val selectedDate = datePickerState.selectedDateMillis?.let { + convertMillisToDate(it) + } ?: "" + + var showTimePicker by remember { mutableStateOf(false) } + + val timePickerState = rememberTimePickerState( + initialHour = viewModel.initialHour, + initialMinute = viewModel.initialMinute, + is24Hour = true, + ) + + var selectedTime by remember { mutableStateOf( + convertTimeToString(timePickerState.hour, timePickerState.minute, 0) + )} + + Column( + horizontalAlignment = Alignment.End + ) { + BottomSheetHeader( + painterResource(id = R.drawable.ic_date_time), + screenTitle = stringResource(id = R.string.add_date_time) + ) + + DatePicker( + state = datePickerState, + showModeToggle = false, + title = null, + headline = null, + colors = DatePickerDefaults.colors( + selectedDayContainerColor = blue900, + dayContentColor = Color.Black, + todayContentColor = Color.Black, + todayDateBorderColor = blue900, + selectedYearContainerColor = blue900, + yearContentColor = Color.Black, + currentYearContentColor = Color.Black + ) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Time", + style = TextStyle( + fontSize = 17.sp, + lineHeight = 22.sp, + fontWeight = FontWeight(600), + color = Color(0xFF000000) + ) + ) + + Text( + text = selectedTime, + style = TextStyle( + fontSize = 17.sp, + lineHeight = 22.sp, + fontWeight = FontWeight(400), + color = Color(0xFF000000), + + ), + modifier = Modifier + .clickable { + showTimePicker = true + } + .background(color = Color(0x1F767680), shape = RoundedCornerShape(size = 8.dp)) + .padding(start = 12.dp, top = 6.dp, end = 12.dp, bottom = 6.dp) + ) + } + + Row( + modifier = Modifier.padding(24.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + Button(modifier = Modifier + .fillMaxWidth() + .weight(1.0f) + .height(48.dp), + shape = RoundedCornerShape(0.dp), + colors = ButtonDefaults.buttonColors(containerColor = superLightBlue), + onClick = { + cancel() + }) { + Text( + text = stringResource(R.string.button_cancel), + fontSize = 14.sp, + color = blue900, + fontFamily = regularFont, + ) + } + + Button(modifier = Modifier + .fillMaxWidth() + .weight(1.0f) + .height(48.dp), + shape = RoundedCornerShape(0.dp), + colors = ButtonDefaults.buttonColors(containerColor = blue900), + onClick = { + openAlertDialog.value = true + }) { + if (viewModel.isBusy.value) { + CircularProgressIndicator( + modifier = Modifier.width(32.dp), + color = blue900, + trackColor = superLightBlue, + ) + } else { + Text( + text = stringResource(R.string.add_date), + fontSize = 14.sp, + fontFamily = regularFont, + ) + } + } + } + + if (showTimePicker) { + TimePickerDialog( + onDismiss = { showTimePicker = false }, + onConfirm = { + showTimePicker = false + selectedTime = convertTimeToString(timePickerState.hour, timePickerState.minute, 0) + } + ) { + TimePicker( + state = timePickerState, + colors = TimePickerDefaults.colors( + timeSelectorSelectedContainerColor = blue900, + timeSelectorSelectedContentColor = Color.White, + selectorColor = blue900, + periodSelectorSelectedContainerColor = blue900, + periodSelectorSelectedContentColor = Color.White + ) + ) + } + } + + when { + openAlertDialog.value -> { + CustomDialog( + title = stringResource(id = R.string.date_confirmation_title), + subtitle = stringResource(id = R.string.date_confirmation_substring), + okButtonText = stringResource(id = R.string.set_date_and_time), + cancelButtonText = stringResource(id = R.string.button_cancel), + onConfirm = { + openAlertDialog.value = false + viewModel.updateDate(dateString = "${selectedDate}T${selectedTime}") + }) { + openAlertDialog.value = false + } + } + } + } + + LaunchedEffect(key1 = viewModel.shouldClose.value, block = { + if (viewModel.shouldClose.value) { + cancel() + } + }) +} + +@Composable +fun TimePickerDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit, + content: @Composable () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + dismissButton = { + TextButton(onClick = { onDismiss() }) { + Text(stringResource(id = R.string.button_cancel)) + } + }, + confirmButton = { + TextButton(onClick = { onConfirm() }) { + Text(stringResource(id = R.string.save_button)) + } + }, + text = { content() } + ) +} + +fun convertMillisToDate(millis: Long): String { + val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + return formatter.format(Date(millis)) +} + +fun convertTimeToString(hour: Int, min: Int, sec: Int): String { + val hourString = String.format("%02d", hour) + val minString = String.format("%02d", min) + val secString = String.format("%02d", sec) + + return "$hourString:$minString:$secString" +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/compose/EditMetadataScreen.kt b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/compose/EditMetadataScreen.kt index 0ee4f166..6c4b9100 100644 --- a/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/compose/EditMetadataScreen.kt +++ b/app/src/main/java/org/permanent/permanent/ui/bulkEditMetadata/compose/EditMetadataScreen.kt @@ -91,6 +91,7 @@ fun EditMetadataScreen( val showApplyAllToSelection by viewModel.showApplyAllToSelection.observeAsState() val isBusy by viewModel.getIsBusy().observeAsState() val locationMenuName by viewModel.getLocationMenuName().observeAsState() + val dateMenuName by viewModel.getDateMenuName().observeAsState() val coroutineScope = rememberCoroutineScope() val snackbarEventFlow = remember { MutableSharedFlow() } @@ -198,6 +199,16 @@ fun EditMetadataScreen( Divider(modifier = Modifier.padding(vertical = 16.dp)) + dateMenuName?.let { + FilesMenuView(icon = R.drawable.ic_date_time, + title = it, + actionTitle = stringResource(id = R.string.menu_toolbar_public_add)) { + openDateAndTimeScreen(viewModel.getRecords()) + } + } + + Divider(modifier = Modifier.padding(vertical = 16.dp)) + locationMenuName?.let { FilesMenuView(icon = R.drawable.map_icon, title = it, diff --git a/app/src/main/java/org/permanent/permanent/viewmodels/EditDateTimeViewModel.kt b/app/src/main/java/org/permanent/permanent/viewmodels/EditDateTimeViewModel.kt new file mode 100644 index 00000000..b75cd1fd --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/viewmodels/EditDateTimeViewModel.kt @@ -0,0 +1,85 @@ +package org.permanent.permanent.viewmodels + +import android.app.Application +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.MutableLiveData +import org.permanent.permanent.models.Record +import org.permanent.permanent.network.IResponseListener +import org.permanent.permanent.repositories.FileRepositoryImpl +import org.permanent.permanent.repositories.IFileRepository +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class EditDateTimeViewModel(application: Application) : ObservableAndroidViewModel(application) { + private var fileRepository: IFileRepository = FileRepositoryImpl(application) + private var records: MutableList = mutableListOf() + var shouldClose: MutableState = mutableStateOf(false) + var isBusy: MutableState = mutableStateOf(false) + val showMessage = mutableStateOf("") + + private val onDateChanged = MutableLiveData() + private val currentTime = Calendar.getInstance() + var initialDateMilis: Long = currentTime.timeInMillis + var initialHour: Int = currentTime.get(Calendar.HOUR_OF_DAY) + var initialMinute = currentTime.get(Calendar.MINUTE) + + fun setRecords(records: ArrayList) { + this.records.addAll(records) + records.firstOrNull()?.displayDate?.let { + val dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + extractDateHourMinute(it, dateFormat) + } + } + + fun updateDate(dateString: String) { + this.records.forEach { + it.displayDate = dateString + } + applyChanges(dateString) + } + + private fun applyChanges(dateString: String) { + if (isBusy.value != null && isBusy.value!!) { + return + } + isBusy.value = true + fileRepository.updateMultipleRecords(records = records, + isFolderRecordType = false, + object : IResponseListener { + override fun onSuccess(message: String?) { + isBusy.value = false + shouldClose.value = true + onDateChanged.value = dateString + } + + override fun onFailed(error: String?) { + isBusy.value = false + error?.let { + showMessage.value = it + } + } + }) + } + + fun getOnDateChanged() = onDateChanged + + private fun extractDateHourMinute(dateString: String, dateFormat: String) { + val simpleDateFormat = SimpleDateFormat(dateFormat, Locale.getDefault()) + val date = simpleDateFormat.parse(dateString) + + val hourFormatter = SimpleDateFormat("HH", Locale.getDefault()) + val minuteFormatter = SimpleDateFormat("mm", Locale.getDefault()) + + date?.time?.let { + initialDateMilis = it + } + hourFormatter.format(date).toIntOrNull()?.let { + initialHour = it + } + minuteFormatter.format(date).toIntOrNull()?.let { + initialMinute = it + } + } +} diff --git a/app/src/main/java/org/permanent/permanent/viewmodels/EditMetadataViewModel.kt b/app/src/main/java/org/permanent/permanent/viewmodels/EditMetadataViewModel.kt index e1f22f1e..4862dce4 100644 --- a/app/src/main/java/org/permanent/permanent/viewmodels/EditMetadataViewModel.kt +++ b/app/src/main/java/org/permanent/permanent/viewmodels/EditMetadataViewModel.kt @@ -33,6 +33,7 @@ class EditMetadataViewModel(application: Application) : ObservableAndroidViewMod val showApplyAllToSelection = MutableLiveData(true) private val isBusy = MutableLiveData(false) private val locationMenuName = MutableLiveData(appContext.getString(R.string.locations)) + private val dateMenuName = MutableLiveData(appContext.getString(R.string.date_time)) private var fileDataSize = 0 private var tagRepository: ITagRepository = TagRepositoryImpl(application) private var fileRepository: IFileRepository = FileRepositoryImpl(application) @@ -55,6 +56,17 @@ class EditMetadataViewModel(application: Application) : ObservableAndroidViewMod } } + fun setDateMenuName() { + records.firstOrNull()?.fileData?.displayDate?.let { firstDate: String -> + var sameDate = records.all { it.fileData?.displayDate == firstDate} + if (sameDate) { + dateMenuName.value = records.firstOrNull()?.fileData?.displayDate + } else { + dateMenuName.value = appContext.getString(R.string.various_date_times) + } + } + } + private fun requestFileData(record: Record) { val folderLinkId = record.folderLinkId val recordId = record.recordId @@ -71,6 +83,7 @@ class EditMetadataViewModel(application: Application) : ObservableAndroidViewMod checkForCommonDescription() checkForCommonTags() setLocationMenuName() + setDateMenuName() } } @@ -243,6 +256,10 @@ class EditMetadataViewModel(application: Application) : ObservableAndroidViewMod locationMenuName.value = address } + fun onDateChanged(dateString: String) { + dateMenuName.value = dateString + } + fun getIsBusy() = isBusy fun getRecords() = records @@ -252,4 +269,6 @@ class EditMetadataViewModel(application: Application) : ObservableAndroidViewMod fun getSomeFilesHaveDescription() = showWarningSomeFilesHaveDescription fun getLocationMenuName() = locationMenuName + + fun getDateMenuName() = dateMenuName } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 92027dfc..8de8d90c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -724,5 +724,12 @@ Various locations New location Are you sure you want set a new location for selected items? + Add date and time + Add date + Various dates and times + New Date & Time + Are you sure you want set a new date & time for selected items? + Set date and time +