diff --git a/android/Omnivore/app/src/main/graphql/schema.graphqls b/android/Omnivore/app/src/main/graphql/schema.graphqls index fd10083a7..4f51a59b3 100644 --- a/android/Omnivore/app/src/main/graphql/schema.graphqls +++ b/android/Omnivore/app/src/main/graphql/schema.graphqls @@ -1720,6 +1720,8 @@ input SaveUrlInput { source: String! state: ArticleSavingRequestStatus url: String! + timezone: String + locale: String } type SearchError { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemMutations.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemMutations.kt index b61c20d03..20ee0a878 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemMutations.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemMutations.kt @@ -3,6 +3,7 @@ package app.omnivore.omnivore.networking import android.content.ContentValues import android.net.Uri import android.util.Log +import androidx.compose.ui.text.intl.Locale import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.Constants import app.omnivore.omnivore.graphql.generated.SaveUrlMutation @@ -17,7 +18,8 @@ import java.util.* suspend fun Networker.deleteSavedItem(itemID: String): Boolean { return try { val input = SetBookmarkArticleInput(itemID, false) - val result = authenticatedApolloClient().mutation(SetBookmarkArticleMutation(input)).execute() + val result = + authenticatedApolloClient().mutation(SetBookmarkArticleMutation(input)).execute() result.data?.setBookmarkArticle?.onSetBookmarkArticleSuccess?.bookmarkedArticle?.id != null } catch (e: java.lang.Exception) { false @@ -32,7 +34,10 @@ suspend fun Networker.unarchiveSavedItem(itemID: String): Boolean { return updateArchiveStatusSavedItem(itemID, false) } -suspend fun Networker.updateArchiveStatusSavedItem(itemID: String, setAsArchived: Boolean): Boolean { +suspend fun Networker.updateArchiveStatusSavedItem( + itemID: String, + setAsArchived: Boolean +): Boolean { return try { val input = ArchiveLinkInput(setAsArchived, itemID) val result = authenticatedApolloClient().mutation(SetLinkArchivedMutation(input)).execute() @@ -45,7 +50,16 @@ suspend fun Networker.updateArchiveStatusSavedItem(itemID: String, setAsArchived suspend fun Networker.saveUrl(url: Uri): Boolean { return try { val clientRequestId = UUID.randomUUID().toString() - val input = SaveUrlInput(url = url.toString(), clientRequestId = clientRequestId, source = "android") + // get locale and timezone from device + val timezone = TimeZone.getDefault().id + val locale = Locale.current.toLanguageTag() + val input = SaveUrlInput( + url = url.toString(), + clientRequestId = clientRequestId, + source = "android", + timezone = Optional.present(timezone), + locale = Optional.present(locale) + ) val result = authenticatedApolloClient().mutation(SaveUrlMutation(input)).execute() result.data?.saveUrl?.onSaveSuccess?.url != null } catch (e: java.lang.Exception) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveSheetActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveSheetActivity.kt index bb39e5eb2..216958bc8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveSheetActivity.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveSheetActivity.kt @@ -52,7 +52,7 @@ class SaveSheetActivity : SaveSheetActivityBase() {} @AndroidEntryPoint @OptIn(ExperimentalMaterialApi::class) -abstract class SaveSheetActivityBase: AppCompatActivity() { +abstract class SaveSheetActivityBase : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,6 +74,7 @@ abstract class SaveSheetActivityBase: AppCompatActivity() { } } } + else -> { // Handle other intents, such as being started from the home screen } @@ -110,8 +111,11 @@ abstract class SaveSheetActivityBase: AppCompatActivity() { .clip(RoundedCornerShape(topEnd = 5.dp, topStart = 5.dp)), containerColor = MaterialTheme.colors.background, actions = { - Spacer(modifier = Modifier.width(25.dp)) - Text(message, style = androidx.compose.material3.MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.width(25.dp)) + Text( + message, + style = androidx.compose.material3.MaterialTheme.typography.titleMedium + ) }, ) }, @@ -181,7 +185,7 @@ abstract class SaveSheetActivityBase: AppCompatActivity() { ) { coroutineScope.launch { if (withResults) setResult(RESULT_OK) - result?.let { intent = it} + result?.let { intent = it } modalBottomSheetState.hide() // will trigger the LaunchedEffect } } @@ -191,9 +195,11 @@ abstract class SaveSheetActivityBase: AppCompatActivity() { viewModel: SaveViewModel, modalBottomSheetState: ModalBottomSheetState ) { - Box(modifier = Modifier - .height(300.dp) - .background(Color.White)) { + Box( + modifier = Modifier + .height(300.dp) + .background(Color.White) + ) { SaveContent(viewModel, modalBottomSheetState, modifier = Modifier.fillMaxSize()) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt index b2376d051..5877d8b03 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt @@ -5,6 +5,7 @@ import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.text.intl.Locale import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -14,6 +15,7 @@ import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.graphql.generated.SaveUrlMutation import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.Optional import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -31,7 +33,7 @@ enum class SaveState { @HiltViewModel class SaveViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository -): ViewModel() { +) : ViewModel() { val saveState = MutableLiveData(SaveState.NONE) var isLoading by mutableStateOf(false) @@ -81,13 +83,18 @@ class SaveViewModel @Inject constructor( try { clientRequestID = UUID.randomUUID().toString() + // get locale and timezone from device + val timezone = TimeZone.getDefault().id + val locale = Locale.current.toLanguageTag() val response = apolloClient.mutation( SaveUrlMutation( SaveUrlInput( clientRequestId = clientRequestID!!, source = "android", - url = cleanedUrl + url = cleanedUrl, + timezone = Optional.present(timezone), + locale = Optional.present(locale) ) ) ).execute() diff --git a/packages/web/components/templates/homeFeed/AddLinkModal.tsx b/packages/web/components/templates/homeFeed/AddLinkModal.tsx index 282033cd6..20b8f300c 100644 --- a/packages/web/components/templates/homeFeed/AddLinkModal.tsx +++ b/packages/web/components/templates/homeFeed/AddLinkModal.tsx @@ -1,5 +1,6 @@ import { useCallback, useState } from 'react' import toast from 'react-hot-toast' +import { locale, timeZone } from '../../../lib/dateFormatting' import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation' import { showErrorToast } from '../../../lib/toastHelpers' import { Button } from '../../elements/Button' @@ -21,8 +22,8 @@ export function AddLinkModal(props: AddLinkModalProps): JSX.Element { const [link, setLink] = useState('') const handleLinkSubmission = useCallback( - async (link: string) => { - const result = await saveUrlMutation(link) + async (link: string, timezone: string, locale: string) => { + const result = await saveUrlMutation(link, timezone, locale) if (result) { toast( () => ( @@ -95,7 +96,7 @@ export function AddLinkModal(props: AddLinkModalProps): JSX.Element { setLink(newLink) submitLink = newLink } - handleLinkSubmission(submitLink) + handleLinkSubmission(submitLink, timeZone, locale) props.onOpenChange(false) }} > diff --git a/packages/web/lib/dateFormatting.ts b/packages/web/lib/dateFormatting.ts index 64e91d917..e31384041 100644 --- a/packages/web/lib/dateFormatting.ts +++ b/packages/web/lib/dateFormatting.ts @@ -1,8 +1,8 @@ //https://github.com/you-dont-need/You-Dont-Need-Momentjs -const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en-US' +export const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en-US' // get the user's time zone -const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone +export const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone export function formattedLongDate(rawDate: string): string { return new Intl.DateTimeFormat(locale, { diff --git a/packages/web/lib/networking/mutations/saveUrlMutation.ts b/packages/web/lib/networking/mutations/saveUrlMutation.ts index 524ac2add..0f3880d15 100644 --- a/packages/web/lib/networking/mutations/saveUrlMutation.ts +++ b/packages/web/lib/networking/mutations/saveUrlMutation.ts @@ -20,7 +20,9 @@ export type SaveUrlData = { } export async function saveUrlMutation( - url: string + url: string, + timezone?: string, + locale?: string ): Promise { const clientRequestId = uuidv4() const mutation = gql` @@ -44,6 +46,8 @@ export async function saveUrlMutation( url, clientRequestId, source: 'add-link', + timezone, + locale, }, }) const output = data as SaveResponseData | undefined diff --git a/packages/web/pages/api/save.ts b/packages/web/pages/api/save.ts index 87e8bfbe7..556b674d5 100644 --- a/packages/web/pages/api/save.ts +++ b/packages/web/pages/api/save.ts @@ -1,5 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next' import { v4 as uuidv4 } from 'uuid' +import { locale, timeZone } from '../../lib/dateFormatting' import { SaveResponseData } from '../../lib/networking/mutations/saveUrlMutation' import { ssrFetcher } from '../../lib/networking/networkHelpers' @@ -7,7 +8,9 @@ const saveUrl = async ( req: NextApiRequest, url: URL, labels: string[] | undefined, - state: string | undefined + state: string | undefined, + timezone?: string, + locale?: string ) => { const clientRequestId = uuidv4() const mutation = ` @@ -33,6 +36,8 @@ const saveUrl = async ( source: 'api-save-url', labels: labels?.map((label) => ({ name: label })), state, + timezone, + locale, }, }) @@ -60,7 +65,7 @@ export default async ( const labels = req.query['labels'] as string[] | undefined const state = req.query['state'] as string | undefined const url = new URL(urlStr as string) - const saveResult = await saveUrl(req, url, labels, state) + const saveResult = await saveUrl(req, url, labels, state, timeZone, locale) console.log('saveResult: ', saveResult) if (saveResult) { res.redirect(`/article?url=${encodeURIComponent(url.toString())}`)