From d3026ccca0deb1b6023064d256ca22f4b0bec2e7 Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:44:33 -0500 Subject: [PATCH 1/2] Add useAppToast, add haptics, present below app navigation bars Resolves #147 --- package.json | 4 +- pnpm-lock.yaml | 30 ++++++------ src/features/auth/Login.tsx | 40 +++++++--------- src/features/comment/CommentEllipsis.tsx | 24 ++++------ src/features/comment/CommentExpander.tsx | 13 ++--- src/features/comment/Comments.tsx | 14 ++---- .../comment/compose/edit/CommentEdit.tsx | 13 ++--- .../comment/compose/reply/CommentReply.tsx | 13 ++--- src/features/community/MoreActions.tsx | 22 ++++----- src/features/gallery/GalleryMoreActions.tsx | 30 +++++------- src/features/gallery/GalleryPostActions.tsx | 9 ++-- src/features/inbox/InboxItem.tsx | 9 ++-- src/features/labels/Vote.tsx | 9 ++-- src/features/labels/links/CommunityLink.tsx | 5 +- src/features/post/new/PostEditorRoot.tsx | 25 ++++------ src/features/post/shared/MoreActions.tsx | 14 +++--- src/features/post/shared/SaveButton.tsx | 7 +-- src/features/post/shared/VoteButton.tsx | 7 +-- src/features/report/Report.tsx | 10 ++-- .../markdown/editing/MarkdownToolbar.tsx | 9 ++-- .../shared/sliding/BaseSlidingVote.tsx | 20 ++++---- src/features/user/useUserDetails.tsx | 8 ++-- src/helpers/toast.ts | 29 ++++++++++++ src/helpers/toastMessages.ts | 37 +++++---------- src/helpers/useAppToast.ts | 47 +++++++++++++++++++ src/helpers/useHapticFeedback.ts | 11 +++-- src/pages/inbox/ComposeButton.tsx | 8 ++-- src/pages/inbox/ConversationPage.tsx | 8 ++-- src/pages/settings/RedditDataMigratePage.tsx | 12 ++--- 29 files changed, 249 insertions(+), 238 deletions(-) create mode 100644 src/helpers/toast.ts create mode 100644 src/helpers/useAppToast.ts diff --git a/package.json b/package.json index a1cb5553e6..d7ea7600e1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "pnpm": { "overrides": { - "@ionic/core": "npm:voyager-ionic-core@^7.4.3" + "@ionic/core": "npm:voyager-ionic-core@^7.5.0" } }, "dependencies": { @@ -47,7 +47,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@github/markdown-toolbar-element": "^2.2.1", - "@ionic/core": "npm:voyager-ionic-core@^7.4.3", + "@ionic/core": "npm:voyager-ionic-core@^7.5.0", "@ionic/react": "^7.4.3", "@ionic/react-router": "^7.4.3", "@reduxjs/toolkit": "^1.9.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92487fb8d0..8d7bcc95e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - '@ionic/core': npm:voyager-ionic-core@^7.4.3 + '@ionic/core': npm:voyager-ionic-core@^7.5.0 dependencies: compression: @@ -77,8 +77,8 @@ devDependencies: specifier: ^2.2.1 version: 2.2.1 '@ionic/core': - specifier: npm:voyager-ionic-core@^7.4.3 - version: /voyager-ionic-core@7.4.3 + specifier: npm:voyager-ionic-core@^7.5.0 + version: /voyager-ionic-core@7.5.0 '@ionic/react': specifier: ^7.4.3 version: 7.4.3(react-dom@18.2.0)(react@18.2.0) @@ -2081,7 +2081,7 @@ packages: react: '>=16.8.6' react-dom: '>=16.8.6' dependencies: - '@ionic/core': /voyager-ionic-core@7.4.3 + '@ionic/core': /voyager-ionic-core@7.5.0 ionicons: 7.1.2 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -2714,8 +2714,8 @@ packages: hasBin: true dev: true - /@stencil/core@4.4.0: - resolution: {integrity: sha512-YlLyCqGBsMEuZb3XTO/STT0TX9eSwjoVhCJgtjVfQOF+ebIMVlojTh40CmDveWiWbth687cbr6S2heeussV8Sg==} + /@stencil/core@4.4.1: + resolution: {integrity: sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==} engines: {node: '>=16.0.0', npm: '>=7.10.0'} hasBin: true dev: true @@ -6781,16 +6781,16 @@ packages: engines: {node: '>= 0.10'} dev: true - /ionicons@7.1.0: - resolution: {integrity: sha512-iE4GuEdEHARJpp0sWL7WJZCzNCf5VxpNRhAjW0fLnZPnNL5qZOJUcfup2Z2Ty7Jk8Q5hacrHfGEB1lCwOdXqGg==} + /ionicons@7.1.2: + resolution: {integrity: sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==} dependencies: '@stencil/core': 2.22.3 dev: true - /ionicons@7.1.2: - resolution: {integrity: sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==} + /ionicons@7.2.1: + resolution: {integrity: sha512-2pvCM7DGVEtbbj48PJzQrCADCQrqjU1nUYX9l9PyEWz3ZfdnLdAouqwPxLdl8tbaF9cE7OZRSlyQD7oLOLnGoQ==} dependencies: - '@stencil/core': 2.22.3 + '@stencil/core': 4.4.1 dev: true /ip@1.1.8: @@ -11444,11 +11444,11 @@ packages: '@capacitor/core': 5.2.3 dev: true - /voyager-ionic-core@7.4.3: - resolution: {integrity: sha512-fG9M+gZVQ1/eMeDm4WFMNoxii7bHRIn8GFWnyRFHB9M2knQO0cWgUZMTlFalal3N0OAzIfLFL4xTGNI+n8e57A==} + /voyager-ionic-core@7.5.0: + resolution: {integrity: sha512-3Jd8Vcg+qByWP5vwn9J6iNFegPzOHJ3Z3hInEf7esmzWKn6AKrrTwARC3dR3RVIJ3U6boaYicyQmdEshMhbfAg==} dependencies: - '@stencil/core': 4.4.0 - ionicons: 7.1.0 + '@stencil/core': 4.4.1 + ionicons: 7.2.1 tslib: 2.6.2 dev: true diff --git a/src/features/auth/Login.tsx b/src/features/auth/Login.tsx index 9fa6f27b08..afb09cb71e 100644 --- a/src/features/auth/Login.tsx +++ b/src/features/auth/Login.tsx @@ -13,7 +13,6 @@ import { IonRadio, IonSpinner, IonList, - useIonToast, IonText, IonRouterLink, useIonModal, @@ -28,6 +27,7 @@ import { preventPhotoswipeGalleryFocusTrap } from "../gallery/GalleryImg"; import { getCustomServers } from "../../services/app"; import { isNative } from "../../helpers/device"; import { Browser } from "@capacitor/browser"; +import useAppToast from "../../helpers/useAppToast"; const JOIN_LEMMY_URL = "https://join-lemmy.org/instances"; @@ -53,7 +53,7 @@ export default function Login({ }: { onDismiss: (data?: string | null | undefined | number, role?: string) => void; }) { - const [present] = useIonToast(); + const presentToast = useAppToast(); const dispatch = useAppDispatch(); const [servers] = useState(getCustomServers()); const [server, setServer] = useState(servers[0]); @@ -104,11 +104,10 @@ export default function Login({ async function submit() { if (!server && !customServer) { - present({ - message: `Please enter your instance domain name`, - duration: 3500, - position: "bottom", + presentToast({ + message: "Please enter your instance domain name", color: "danger", + fullscreen: true, }); return; } @@ -116,11 +115,10 @@ export default function Login({ if (!serverConfirmed) { if (customServer) { if (!customServerHostname) { - present({ + presentToast({ message: `${customServer} is not a valid server URL. Please try again`, - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); return; @@ -130,11 +128,10 @@ export default function Login({ try { await getClient(customServerHostname).getSite({}); } catch (error) { - present({ + presentToast({ message: `Problem connecting to ${customServerHostname}. Please try again`, - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); throw error; @@ -148,21 +145,19 @@ export default function Login({ } if (!username || !password) { - present({ + presentToast({ message: "Please fill out username and password fields", - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); return; } if (!totp && needsTotp) { - present({ + presentToast({ message: `Please enter your second factor authentication code for ${username}`, - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); return; } @@ -183,11 +178,10 @@ export default function Login({ setPassword(""); } - present({ + presentToast({ message: getLoginErrorMessage(error, server ?? customServer), - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); throw error; @@ -196,10 +190,8 @@ export default function Login({ } onDismiss(); - present({ + presentToast({ message: "Login successful", - duration: 2000, - position: "bottom", color: "success", }); } diff --git a/src/features/comment/CommentEllipsis.tsx b/src/features/comment/CommentEllipsis.tsx index a3e64a401a..59999f7960 100644 --- a/src/features/comment/CommentEllipsis.tsx +++ b/src/features/comment/CommentEllipsis.tsx @@ -1,10 +1,5 @@ import styled from "@emotion/styled"; -import { - IonIcon, - useIonActionSheet, - useIonRouter, - useIonToast, -} from "@ionic/react"; +import { IonIcon, useIonActionSheet, useIonRouter } from "@ionic/react"; import { arrowDownOutline, arrowUndoOutline, @@ -36,6 +31,7 @@ import { handleSelector, isDownvoteEnabledSelector } from "../auth/authSlice"; import { CommentsContext } from "./CommentsContext"; import { deleteComment, saveComment, voteOnComment } from "./commentSlice"; import useCollapseRootComment from "./useCollapseRootComment"; +import useAppToast from "../../helpers/useAppToast"; const StyledIonIcon = styled(IonIcon)` padding: 8px 12px; @@ -57,7 +53,7 @@ export default function MoreActions({ const dispatch = useAppDispatch(); const { prependComments } = useContext(CommentsContext); const myHandle = useAppSelector(handleSelector); - const [present] = useIonToast(); + const presentToast = useAppToast(); const [presentActionSheet] = useIonActionSheet(); const [presentSecondaryActionSheet] = useIonActionSheet(); const collapseRootComment = useCollapseRootComment(commentView, rootIndex); @@ -105,7 +101,7 @@ export default function MoreActions({ try { await dispatch(voteOnComment(comment.id, myVote === 1 ? 0 : 1)); } catch (error) { - present(voteError); + presentToast(voteError); } })(); }, @@ -123,7 +119,7 @@ export default function MoreActions({ voteOnComment(comment.id, myVote === -1 ? 0 : -1), ); } catch (error) { - present(voteError); + presentToast(voteError); } })(); }, @@ -139,7 +135,7 @@ export default function MoreActions({ try { await dispatch(saveComment(comment.id, !mySaved)); } catch (error) { - present(saveError); + presentToast(saveError); } })(); }, @@ -168,21 +164,17 @@ export default function MoreActions({ try { await dispatch(deleteComment(comment.id)); } catch (error) { - present({ + presentToast({ message: "Problem deleting comment. Please try again.", - duration: 3500, - position: "bottom", color: "danger", }); throw error; } - present({ + presentToast({ message: "Comment deleted!", - duration: 3500, - position: "bottom", color: "primary", }); })(); diff --git a/src/features/comment/CommentExpander.tsx b/src/features/comment/CommentExpander.tsx index a2a37e042f..e434644542 100644 --- a/src/features/comment/CommentExpander.tsx +++ b/src/features/comment/CommentExpander.tsx @@ -5,11 +5,12 @@ import CommentHr from "./CommentHr"; import { useContext, useState } from "react"; import { CommentsContext } from "./CommentsContext"; import useClient from "../../helpers/useClient"; -import { IonIcon, IonSpinner, useIonToast } from "@ionic/react"; +import { IonIcon, IonSpinner } from "@ionic/react"; import { chevronDown } from "ionicons/icons"; import AnimateHeight from "react-animate-height"; import { MAX_DEFAULT_COMMENT_DEPTH } from "../../helpers/lemmy"; import { css } from "@emotion/react"; +import useAppToast from "../../helpers/useAppToast"; const MoreRepliesBlock = styled.div<{ hidden: boolean }>` display: flex; @@ -53,7 +54,7 @@ export default function CommentExpander({ missing, collapsed, }: CommentExpanderProps) { - const [present] = useIonToast(); + const presentToast = useAppToast(); const { appendComments } = useContext(CommentsContext); const client = useClient(); const [loading, setLoading] = useState(false); @@ -72,10 +73,8 @@ export default function CommentExpander({ max_depth: Math.max((depth += 2), MAX_DEFAULT_COMMENT_DEPTH), }); } catch (error) { - present({ + presentToast({ message: "Problem fetching more comments. Please try again.", - duration: 3500, - position: "bottom", color: "danger", }); throw error; @@ -84,10 +83,8 @@ export default function CommentExpander({ } if (response.comments.length === 0) { - present({ + presentToast({ message: `Uh-oh. Looks like Lemmy returned 0 comments, but there's actually ${missing}`, - duration: 3500, - position: "bottom", color: "danger", }); return; diff --git a/src/features/comment/Comments.tsx b/src/features/comment/Comments.tsx index a3eff1360e..622d7c0600 100644 --- a/src/features/comment/Comments.tsx +++ b/src/features/comment/Comments.tsx @@ -11,12 +11,7 @@ import { buildCommentsTreeWithMissing, } from "../../helpers/lemmy"; import CommentTree from "./CommentTree"; -import { - IonRefresher, - IonRefresherContent, - IonSpinner, - useIonToast, -} from "@ionic/react"; +import { IonRefresher, IonRefresherContent, IonSpinner } from "@ionic/react"; import styled from "@emotion/styled"; import { css } from "@emotion/react"; import { CommentSortType, CommentView, Person } from "lemmy-js-client"; @@ -32,6 +27,7 @@ import { CommentsContext } from "./CommentsContext"; import { jwtSelector } from "../auth/authSlice"; import { defaultCommentDepthSelector } from "../settings/settingsSlice"; import { isSafariFeedHackEnabled } from "../../pages/shared/FeedContent"; +import useAppToast from "../../helpers/useAppToast"; const centerCss = css` position: relative; @@ -92,7 +88,7 @@ export default forwardRef(function Comments( ); const client = useClient(); const [isListAtTop, setIsListAtTop] = useState(true); - const [present] = useIonToast(); + const presentToast = useAppToast(); const defaultCommentDepth = useAppSelector(defaultCommentDepthSelector); const highlightedCommentId = commentPath @@ -151,10 +147,8 @@ export default forwardRef(function Comments( }); } catch (error) { if (reqPostId === postId && reqCommentId === commentId) - present({ + presentToast({ message: "Problem fetching comments. Please try again.", - duration: 3500, - position: "bottom", color: "danger", }); diff --git a/src/features/comment/compose/edit/CommentEdit.tsx b/src/features/comment/compose/edit/CommentEdit.tsx index dc0e8a1ba2..56279fddd2 100644 --- a/src/features/comment/compose/edit/CommentEdit.tsx +++ b/src/features/comment/compose/edit/CommentEdit.tsx @@ -4,7 +4,6 @@ import { IonHeader, IonToolbar, IonTitle, - useIonToast, } from "@ionic/react"; import { Comment } from "lemmy-js-client"; import { useEffect, useState } from "react"; @@ -14,6 +13,7 @@ import { jwtSelector } from "../../../auth/authSlice"; import { editComment } from "../../commentSlice"; import { DismissableProps } from "../../../shared/DynamicDismissableModal"; import CommentContent from "../shared"; +import useAppToast from "../../../../helpers/useAppToast"; type CommentEditingProps = DismissableProps & { item: Comment; @@ -27,7 +27,7 @@ export default function CommentEdit({ const dispatch = useAppDispatch(); const [replyContent, setReplyContent] = useState(item.content); const jwt = useAppSelector(jwtSelector); - const [present] = useIonToast(); + const presentToast = useAppToast(); const [loading, setLoading] = useState(false); const isSubmitDisabled = !replyContent.trim() || item.content === replyContent || loading; @@ -45,11 +45,10 @@ export default function CommentEdit({ try { await dispatch(editComment(item.id, replyContent)); } catch (error) { - present({ + presentToast({ message: "Problem saving your changes. Please try again.", - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); throw error; @@ -57,10 +56,8 @@ export default function CommentEdit({ setLoading(false); } - present({ + presentToast({ message: "Comment edited!", - duration: 3500, - position: "bottom", color: "success", }); diff --git a/src/features/comment/compose/reply/CommentReply.tsx b/src/features/comment/compose/reply/CommentReply.tsx index d7bfaac510..a0eddb5137 100644 --- a/src/features/comment/compose/reply/CommentReply.tsx +++ b/src/features/comment/compose/reply/CommentReply.tsx @@ -5,7 +5,6 @@ import { IonHeader, IonToolbar, IonTitle, - useIonToast, IonText, } from "@ionic/react"; import { @@ -25,6 +24,7 @@ import CommentContent from "../shared"; import useTextRecovery, { clearRecoveredText, } from "../../../../helpers/useTextRecovery"; +import useAppToast from "../../../../helpers/useAppToast"; export const UsernameIonText = styled(IonText)` font-size: 0.7em; @@ -61,7 +61,7 @@ export default function CommentReply({ const [replyContent, setReplyContent] = useState(""); const client = useClient(); const jwt = useAppSelector(jwtSelector); - const [present] = useIonToast(); + const presentToast = useAppToast(); const [loading, setLoading] = useState(false); const userHandle = useAppSelector(handleSelector); const isSubmitDisabled = !replyContent.trim() || loading; @@ -87,11 +87,10 @@ export default function CommentReply({ ? "Please select a language in your lemmy profile settings." : "Please try again."; - present({ + presentToast({ message: `Problem posting your comment. ${errorDescription}`, - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); throw error; @@ -99,10 +98,8 @@ export default function CommentReply({ setLoading(false); } - present({ + presentToast({ message: "Comment posted!", - duration: 3500, - position: "bottom", color: "success", }); diff --git a/src/features/community/MoreActions.tsx b/src/features/community/MoreActions.tsx index 1ebcfa143e..618b3c56e2 100644 --- a/src/features/community/MoreActions.tsx +++ b/src/features/community/MoreActions.tsx @@ -4,7 +4,6 @@ import { IonIcon, useIonActionSheet, useIonRouter, - useIonToast, } from "@ionic/react"; import { createOutline, @@ -40,13 +39,14 @@ import { buildSuccessSubscribing, } from "../../helpers/toastMessages"; import useHidePosts from "../feed/useHidePosts"; +import useAppToast from "../../helpers/useAppToast"; interface MoreActionsProps { community: string; } export default function MoreActions({ community }: MoreActionsProps) { - const [present] = useIonToast(); + const presentToast = useAppToast(); const router = useIonRouter(); const dispatch = useAppDispatch(); const [open, setOpen] = useState(false); @@ -155,21 +155,19 @@ export default function MoreActions({ community }: MoreActionsProps) { try { await dispatch(followCommunity(!isSubscribed, communityId)); } catch (error) { - present(buildProblemSubscribing(isSubscribed, community)); + presentToast(buildProblemSubscribing(isSubscribed, community)); throw error; } - present(buildSuccessSubscribing(isSubscribed, community)); + presentToast(buildSuccessSubscribing(isSubscribed, community)); break; } case "post": { if (presentLoginIfNeeded()) return; if (!canPost) { - present({ + presentToast({ message: "This community has disabled new posts", - duration: 3500, - position: "bottom", color: "warning", }); return; @@ -191,12 +189,10 @@ export default function MoreActions({ community }: MoreActionsProps) { dispatch(removeFavorite(community)); } - present({ + presentToast({ message: `${ isFavorite ? "Unfavorited" : "Favorited" } c/${community}.`, - duration: 3500, - position: "bottom", color: "success", }); @@ -229,7 +225,7 @@ export default function MoreActions({ community }: MoreActionsProps) { (async () => { await dispatch(showNsfw(false)); - present(allNSFWHidden); + presentToast(allNSFWHidden); })(); }, }, @@ -242,7 +238,7 @@ export default function MoreActions({ community }: MoreActionsProps) { blockCommunity(!isBlocked, communityId), ); - present(buildBlocked(!isBlocked, community)); + presentToast(buildBlocked(!isBlocked, community)); })(); }, }, @@ -255,7 +251,7 @@ export default function MoreActions({ community }: MoreActionsProps) { } else { await dispatch(blockCommunity(!isBlocked, communityId)); - present(buildBlocked(!isBlocked, community)); + presentToast(buildBlocked(!isBlocked, community)); } } } diff --git a/src/features/gallery/GalleryMoreActions.tsx b/src/features/gallery/GalleryMoreActions.tsx index 0a8e75e040..37286c14d9 100644 --- a/src/features/gallery/GalleryMoreActions.tsx +++ b/src/features/gallery/GalleryMoreActions.tsx @@ -1,9 +1,4 @@ -import { - IonIcon, - useIonActionSheet, - useIonRouter, - useIonToast, -} from "@ionic/react"; +import { IonIcon, useIonActionSheet, useIonRouter } from "@ionic/react"; import { bookmarkOutline, copyOutline, @@ -27,6 +22,7 @@ import { ActionButton } from "../post/actions/ActionButton"; import { StashMedia } from "capacitor-stash-media"; import { isNative } from "../../helpers/device"; import { Share } from "@capacitor/share"; +import useAppToast from "../../helpers/useAppToast"; interface GalleryMoreActionsProps { post: PostView; @@ -42,7 +38,7 @@ export default function GalleryMoreActions({ const buildGeneralBrowseLink = useBuildGeneralBrowseLink(); const { presentLoginIfNeeded } = useContext(PageContext); - const [presentToast] = useIonToast(); + const presentToast = useAppToast(); const dispatch = useAppDispatch(); const postSavedById = useAppSelector((state) => state.post.postSavedById); @@ -70,9 +66,9 @@ export default function GalleryMoreActions({ } catch (error) { presentToast({ message: "Error sharing photo", - duration: 3500, - position: "bottom", color: "danger", + position: "top", + fullscreen: true, }); throw error; @@ -90,9 +86,9 @@ export default function GalleryMoreActions({ } catch (error) { presentToast({ message: "Error saving photo to device", - duration: 3500, - position: "bottom", color: "danger", + position: "top", + fullscreen: true, }); throw error; @@ -100,9 +96,9 @@ export default function GalleryMoreActions({ presentToast({ message: "Photo saved", - duration: 3500, - position: "bottom", color: "success", + position: "top", + fullscreen: true, }); })(); }, @@ -117,9 +113,9 @@ export default function GalleryMoreActions({ } catch (error) { presentToast({ message: "Error copying photo to clipboard", - duration: 3500, - position: "bottom", color: "danger", + position: "top", + fullscreen: true, }); throw error; @@ -127,9 +123,9 @@ export default function GalleryMoreActions({ presentToast({ message: "Photo copied to clipboard", - duration: 3500, - position: "bottom", color: "success", + position: "top", + fullscreen: true, }); })(); }, diff --git a/src/features/gallery/GalleryPostActions.tsx b/src/features/gallery/GalleryPostActions.tsx index e45a4775ea..95b25e3f69 100644 --- a/src/features/gallery/GalleryPostActions.tsx +++ b/src/features/gallery/GalleryPostActions.tsx @@ -1,4 +1,4 @@ -import { IonIcon, useIonRouter, useIonToast } from "@ionic/react"; +import { IonIcon, useIonRouter } from "@ionic/react"; import { VoteButton } from "../post/shared/VoteButton"; import { PostView } from "lemmy-js-client"; import { chatbubbleOutline, shareOutline } from "ionicons/icons"; @@ -19,6 +19,7 @@ import { isNative } from "../../helpers/device"; import GalleryMoreActions from "./GalleryMoreActions"; import { StashMedia } from "capacitor-stash-media"; import { Share } from "@capacitor/share"; +import useAppToast from "../../helpers/useAppToast"; const Container = styled.div` display: flex; @@ -55,7 +56,7 @@ export default function GalleryPostActions({ ); const router = useIonRouter(); const location = useLocation(); - const [presentToast] = useIonToast(); + const presentToast = useAppToast(); const { close } = useContext(GalleryContext); async function shareImage() { @@ -72,9 +73,9 @@ export default function GalleryPostActions({ } catch (error) { presentToast({ message: "Error sharing photo", - duration: 3500, - position: "bottom", color: "danger", + position: "top", + fullscreen: true, }); throw error; diff --git a/src/features/inbox/InboxItem.tsx b/src/features/inbox/InboxItem.tsx index 57a3f1e772..bb61ecdb4d 100644 --- a/src/features/inbox/InboxItem.tsx +++ b/src/features/inbox/InboxItem.tsx @@ -4,7 +4,7 @@ import { PrivateMessageView, } from "lemmy-js-client"; import CommentMarkdown from "../comment/CommentMarkdown"; -import { IonIcon, IonItem, useIonToast } from "@ionic/react"; +import { IonIcon, IonItem } from "@ionic/react"; import styled from "@emotion/styled"; import { albums, @@ -23,6 +23,7 @@ import { isPostReply } from "../../pages/inbox/RepliesPage"; import { maxWidthCss } from "../shared/AppContent"; import VoteArrow from "./VoteArrow"; import SlidingInbox from "../shared/sliding/SlidingInbox"; +import useAppToast from "../../helpers/useAppToast"; const Hr = styled.div` ${maxWidthCss} @@ -122,7 +123,7 @@ export default function InboxItem({ item }: InboxItemProps) { const readByInboxItemId = useAppSelector( (state) => state.inbox.readByInboxItemId, ); - const [present] = useIonToast(); + const presentToast = useAppToast(); const commentVotesById = useAppSelector( (state) => state.comment.commentVotesById, ); @@ -218,10 +219,8 @@ export default function InboxItem({ item }: InboxItemProps) { try { await dispatch(markReadAction(item, true)); } catch (error) { - present({ + presentToast({ message: "Failed to mark item as read", - duration: 3500, - position: "bottom", color: "danger", }); diff --git a/src/features/labels/Vote.tsx b/src/features/labels/Vote.tsx index f9d71a4fa8..deeaaddf50 100644 --- a/src/features/labels/Vote.tsx +++ b/src/features/labels/Vote.tsx @@ -1,5 +1,5 @@ import { useAppDispatch, useAppSelector } from "../../store"; -import { IonIcon, useIonToast } from "@ionic/react"; +import { IonIcon } from "@ionic/react"; import { arrowDownSharp, arrowUpSharp } from "ionicons/icons"; import styled from "@emotion/styled"; import { voteOnPost } from "../post/postSlice"; @@ -16,6 +16,7 @@ import { OVoteDisplayMode } from "../../services/db"; import { isDownvoteEnabledSelector } from "../auth/authSlice"; import { ImpactStyle } from "@capacitor/haptics"; import useHapticFeedback from "../../helpers/useHapticFeedback"; +import useAppToast from "../../helpers/useAppToast"; const Container = styled.div<{ vote?: 1 | -1 | 0; @@ -44,7 +45,7 @@ interface VoteProps { } export default function Vote({ item }: VoteProps): React.ReactElement { - const [present] = useIonToast(); + const presentToast = useAppToast(); const dispatch = useAppDispatch(); const votesById = useAppSelector((state) => "comment" in item @@ -69,7 +70,7 @@ export default function Vote({ item }: VoteProps): React.ReactElement { // you are allowed to un-downvote if they are disabled if (!canDownvote && vote === -1) { - present(downvotesDisabled); + presentToast(downvotesDisabled); return; } @@ -83,7 +84,7 @@ export default function Vote({ item }: VoteProps): React.ReactElement { try { await dispatch(dispatcherFn(id, vote)); } catch (error) { - present(voteError); + presentToast(voteError); throw error; } } diff --git a/src/features/labels/links/CommunityLink.tsx b/src/features/labels/links/CommunityLink.tsx index ba01fb1dfd..7c2656f934 100644 --- a/src/features/labels/links/CommunityLink.tsx +++ b/src/features/labels/links/CommunityLink.tsx @@ -6,7 +6,7 @@ import { StyledLink } from "./shared"; import ItemIcon from "../img/ItemIcon"; import { css } from "@emotion/react"; import { useAppDispatch, useAppSelector } from "../../../store"; -import { useIonActionSheet, useIonToast } from "@ionic/react"; +import { useIonActionSheet } from "@ionic/react"; import { useLongPress } from "use-long-press"; import { blockCommunity, @@ -24,6 +24,7 @@ import { } from "ionicons/icons"; import { useContext } from "react"; import { PageContext } from "../../auth/PageContext"; +import useAppToast from "../../../helpers/useAppToast"; interface CommunityLinkProps { community: Community; @@ -41,7 +42,7 @@ export default function CommunityLink({ }: CommunityLinkProps) { const dispatch = useAppDispatch(); const [present] = useIonActionSheet(); - const [presentToast] = useIonToast(); + const presentToast = useAppToast(); const { presentLoginIfNeeded } = useContext(PageContext); const communityByHandle = useAppSelector( diff --git a/src/features/post/new/PostEditorRoot.tsx b/src/features/post/new/PostEditorRoot.tsx index ec22f2d61e..d72bb180c6 100644 --- a/src/features/post/new/PostEditorRoot.tsx +++ b/src/features/post/new/PostEditorRoot.tsx @@ -6,7 +6,6 @@ import { IonContent, IonToolbar, IonTitle, - useIonToast, IonText, IonSegment, IonSegmentButton, @@ -34,6 +33,7 @@ import { useBuildGeneralBrowseLink } from "../../../helpers/routes"; import PhotoPreview from "./PhotoPreview"; import { uploadImage } from "../../../services/lemmy"; import { receivedPosts } from "../postSlice"; +import useAppToast from "../../../helpers/useAppToast"; const Container = styled.div` position: absolute; @@ -119,7 +119,7 @@ export default function PostEditorRoot({ const [postType, setPostType] = useState(initialPostType); const client = useClient(); const jwt = useAppSelector(jwtSelector); - const [present] = useIonToast(); + const presentToast = useAppToast(); const [loading, setLoading] = useState(false); const [title, setTitle] = useState(initialTitle); const [url, setUrl] = useState(initialUrl); @@ -222,12 +222,10 @@ export default function PostEditorRoot({ } if (errorMessage) { - present({ + presentToast({ // TODO more helpful msg message: errorMessage, - duration: 3500, - position: "bottom", - color: "primary", + fullscreen: true, }); return; @@ -259,11 +257,10 @@ export default function PostEditorRoot({ }); } } catch (error) { - present({ + presentToast({ message: "Problem submitting your post. Please try again.", - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); throw error; @@ -273,11 +270,10 @@ export default function PostEditorRoot({ dispatch(receivedPosts([postResponse.post_view])); - present({ + presentToast({ message: existingPost ? "Post edited!" : "Post created!", - duration: 3500, - position: "bottom", color: "success", + fullscreen: true, }); setCanDismiss(true); @@ -307,11 +303,10 @@ export default function PostEditorRoot({ try { imageUrl = await uploadImage(instanceUrl, jwt, image); } catch (error) { - present({ + presentToast({ message: "Problem uploading image. Please try again.", - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); clearImage(); diff --git a/src/features/post/shared/MoreActions.tsx b/src/features/post/shared/MoreActions.tsx index 514c672589..559d9a36d2 100644 --- a/src/features/post/shared/MoreActions.tsx +++ b/src/features/post/shared/MoreActions.tsx @@ -3,7 +3,6 @@ import { IonIcon, useIonActionSheet, useIonRouter, - useIonToast, } from "@ionic/react"; import { arrowDownOutline, @@ -42,6 +41,7 @@ import { handleSelector, isDownvoteEnabledSelector, } from "../../auth/authSlice"; +import useAppToast from "../../../helpers/useAppToast"; interface MoreActionsProps { post: PostView; @@ -56,7 +56,7 @@ export default function MoreActions({ }: MoreActionsProps) { const [presentActionSheet] = useIonActionSheet(); const [presentSecondaryActionSheet] = useIonActionSheet(); - const [present] = useIonToast(); + const presentToast = useAppToast(); const buildGeneralBrowseLink = useBuildGeneralBrowseLink(); const dispatch = useAppDispatch(); const isHidden = useAppSelector(postHiddenByIdSelector)[post.post.id]; @@ -95,7 +95,7 @@ export default function MoreActions({ try { await dispatch(voteOnPost(post.post.id, myVote === 1 ? 0 : 1)); } catch (error) { - present(voteError); + presentToast(voteError); throw error; } @@ -115,7 +115,7 @@ export default function MoreActions({ voteOnPost(post.post.id, myVote === -1 ? 0 : -1), ); } catch (error) { - present(voteError); + presentToast(voteError); throw error; } @@ -133,7 +133,7 @@ export default function MoreActions({ try { await dispatch(savePost(post.post.id, !mySaved)); } catch (error) { - present(saveError); + presentToast(saveError); throw error; } @@ -154,10 +154,8 @@ export default function MoreActions({ (async () => { await dispatch(deletePost(post.post.id)); - present({ + presentToast({ message: "Post deleted", - duration: 3500, - position: "bottom", color: "success", }); })(); diff --git a/src/features/post/shared/SaveButton.tsx b/src/features/post/shared/SaveButton.tsx index 1e0aa8e64e..1bbb67038e 100644 --- a/src/features/post/shared/SaveButton.tsx +++ b/src/features/post/shared/SaveButton.tsx @@ -1,5 +1,5 @@ import styled from "@emotion/styled"; -import { IonIcon, useIonToast } from "@ionic/react"; +import { IonIcon } from "@ionic/react"; import { MouseEvent, useContext } from "react"; import { PageContext } from "../../auth/PageContext"; import { useAppDispatch, useAppSelector } from "../../../store"; @@ -10,6 +10,7 @@ import { ActionButton } from "../actions/ActionButton"; import { saveError } from "../../../helpers/toastMessages"; import { ImpactStyle } from "@capacitor/haptics"; import useHapticFeedback from "../../../helpers/useHapticFeedback"; +import useAppToast from "../../../helpers/useAppToast"; export const Item = styled(ActionButton, { shouldForwardProp: (prop) => prop !== "on", @@ -30,7 +31,7 @@ interface SaveButtonProps { } export function SaveButton({ postId }: SaveButtonProps) { - const [present] = useIonToast(); + const presentToast = useAppToast(); const dispatch = useAppDispatch(); const { presentLoginIfNeeded } = useContext(PageContext); const vibrate = useHapticFeedback(); @@ -48,7 +49,7 @@ export function SaveButton({ postId }: SaveButtonProps) { try { await dispatch(savePost(postId, !postSavedById[postId])); } catch (error) { - present(saveError); + presentToast(saveError); throw error; } diff --git a/src/features/post/shared/VoteButton.tsx b/src/features/post/shared/VoteButton.tsx index 5d8ffff0b3..7893005050 100644 --- a/src/features/post/shared/VoteButton.tsx +++ b/src/features/post/shared/VoteButton.tsx @@ -1,5 +1,5 @@ import styled from "@emotion/styled"; -import { IonIcon, useIonToast } from "@ionic/react"; +import { IonIcon } from "@ionic/react"; import { useContext, useEffect } from "react"; import { useAppDispatch, useAppSelector } from "../../../store"; import { voteOnPost } from "../postSlice"; @@ -13,6 +13,7 @@ import { bounceAnimationOnTransition, bounceMs } from "../../shared/animations"; import { useTransition } from "react-transition-state"; import { ImpactStyle } from "@capacitor/haptics"; import useHapticFeedback from "../../../helpers/useHapticFeedback"; +import useAppToast from "../../../helpers/useAppToast"; export const Item = styled(ActionButton, { shouldForwardProp: (prop) => prop !== "on" && prop !== "activeColor", @@ -37,7 +38,7 @@ interface VoteButtonProps { } export function VoteButton({ type, postId }: VoteButtonProps) { - const [present] = useIonToast(); + const presentToast = useAppToast(); const dispatch = useAppDispatch(); const vibrate = useHapticFeedback(); const { presentLoginIfNeeded } = useContext(PageContext); @@ -107,7 +108,7 @@ export function VoteButton({ type, postId }: VoteButtonProps) { voteOnPost(postId, myVote === selectedVote ? 0 : selectedVote), ); } catch (error) { - present(voteError); + presentToast(voteError); throw error; } diff --git a/src/features/report/Report.tsx b/src/features/report/Report.tsx index 87cac416e4..cbbe379d12 100644 --- a/src/features/report/Report.tsx +++ b/src/features/report/Report.tsx @@ -1,10 +1,11 @@ -import { IonActionSheet, IonAlert, useIonToast } from "@ionic/react"; +import { IonActionSheet, IonAlert } from "@ionic/react"; import { CommentView, PostView, PrivateMessageView } from "lemmy-js-client"; import { forwardRef, useImperativeHandle, useState } from "react"; import useClient from "../../helpers/useClient"; import { useAppSelector } from "../../store"; import { jwtSelector } from "../auth/authSlice"; import { IonAlertCustomEvent, OverlayEventDetail } from "@ionic/core"; +import useAppToast from "../../helpers/useAppToast"; export type ReportableItem = CommentView | PostView | PrivateMessageView; @@ -14,7 +15,7 @@ export type ReportHandle = { export const Report = forwardRef(function Report(_, ref) { const jwt = useAppSelector(jwtSelector); - const [presentToast] = useIonToast(); + const presentToast = useAppToast(); const [item, setItem] = useState(); const [reportOptionsOpen, setReportOptionsOpen] = useState(false); const [customOpen, setCustomOpen] = useState(false); @@ -67,8 +68,6 @@ export const Report = forwardRef(function Report(_, ref) { presentToast({ message: `Failed to report ${type?.toLowerCase()}. ${errorDetail}`, - duration: 3500, - position: "bottom", color: "danger", }); @@ -77,9 +76,6 @@ export const Report = forwardRef(function Report(_, ref) { presentToast({ message: `${type} reported!`, - duration: 3500, - position: "bottom", - color: "primary", }); } diff --git a/src/features/shared/markdown/editing/MarkdownToolbar.tsx b/src/features/shared/markdown/editing/MarkdownToolbar.tsx index 78b302fd93..0f1fb45802 100644 --- a/src/features/shared/markdown/editing/MarkdownToolbar.tsx +++ b/src/features/shared/markdown/editing/MarkdownToolbar.tsx @@ -6,7 +6,6 @@ import { IonLoading, useIonActionSheet, useIonModal, - useIonToast, } from "@ionic/react"; import { ellipsisHorizontal, @@ -33,6 +32,7 @@ import { jwtSelector, urlSelector } from "../../../auth/authSlice"; import { insert } from "../../../../helpers/string"; import useKeyboardOpen from "../../../../helpers/useKeyboardOpen"; import textFaces from "./textFaces.txt?raw"; +import useAppToast from "../../../../helpers/useAppToast"; export const TOOLBAR_TARGET_ID = "toolbar-target"; export const TOOLBAR_HEIGHT = "50px"; @@ -110,7 +110,7 @@ export default function MarkdownToolbar({ }: MarkdownToolbarProps) { const [presentActionSheet] = useIonActionSheet(); const [presentTextFaceActionSheet] = useIonActionSheet(); - const [presentAlert] = useIonToast(); + const presentToast = useAppToast(); const keyboardOpen = useKeyboardOpen(); const [imageUploading, setImageUploading] = useState(false); const jwt = useAppSelector(jwtSelector); @@ -170,11 +170,10 @@ export default function MarkdownToolbar({ try { imageUrl = await uploadImage(instanceUrl, jwt, image); } catch (error) { - presentAlert({ + presentToast({ message: "Problem uploading image. Please try again.", - duration: 3500, - position: "bottom", color: "danger", + fullscreen: true, }); throw error; diff --git a/src/features/shared/sliding/BaseSlidingVote.tsx b/src/features/shared/sliding/BaseSlidingVote.tsx index cfedcc78bf..7c74207032 100644 --- a/src/features/shared/sliding/BaseSlidingVote.tsx +++ b/src/features/shared/sliding/BaseSlidingVote.tsx @@ -1,4 +1,3 @@ -import { useIonToast } from "@ionic/react"; import { arrowDownSharp, arrowUndo, @@ -33,6 +32,7 @@ import useCollapseRootComment from "../../comment/useCollapseRootComment"; import { getInboxItemId, markRead } from "../../inbox/inboxSlice"; import { CommentsContext } from "../../comment/CommentsContext"; import styled from "@emotion/styled"; +import useAppToast from "../../../helpers/useAppToast"; const StyledItemContainer = styled.div` --ion-item-border-color: transparent; @@ -93,7 +93,7 @@ function BaseSlidingVoteInternal({ const { presentLoginIfNeeded, presentCommentReply } = useContext(PageContext); const { prependComments } = useContext(CommentsContext); - const [present] = useIonToast(); + const presentToast = useAppToast(); const dispatch = useAppDispatch(); const postVotesById = useAppSelector((state) => state.post.postVotesById); @@ -132,10 +132,10 @@ function BaseSlidingVoteInternal({ if (isPost) await dispatch(voteOnPost(item.post.id, score)); else await dispatch(voteOnComment(item.comment.id, score)); } catch (error) { - present(voteError); + presentToast(voteError); } }, - [dispatch, isPost, item, present, presentLoginIfNeeded], + [dispatch, isPost, item, presentToast, presentLoginIfNeeded], ); const reply = useCallback(async () => { @@ -165,15 +165,13 @@ function BaseSlidingVoteInternal({ try { await dispatch((isPost ? savePost : saveComment)(id, !isSaved)); } catch (error) { - present({ + presentToast({ message: "Failed to mark item as saved", - duration: 3500, - position: "bottom", color: "danger", }); throw error; } - }, [presentLoginIfNeeded, dispatch, isPost, id, isSaved, present]); + }, [presentLoginIfNeeded, dispatch, isPost, id, isSaved, presentToast]); const saveAction = useMemo(() => { return { @@ -221,15 +219,13 @@ function BaseSlidingVoteInternal({ try { await dispatch(markRead(item, !isRead)); } catch (error) { - present({ + presentToast({ message: "Failed to mark item as unread", - duration: 3500, - position: "bottom", color: "danger", }); throw error; } - }, [dispatch, present, item, isRead]); + }, [dispatch, presentToast, item, isRead]); const markUnreadAction = useMemo(() => { return { diff --git a/src/features/user/useUserDetails.tsx b/src/features/user/useUserDetails.tsx index 259a364e26..52ca189e6f 100644 --- a/src/features/user/useUserDetails.tsx +++ b/src/features/user/useUserDetails.tsx @@ -3,8 +3,8 @@ import { useAppDispatch, useAppSelector } from "../../store"; import { getHandle } from "../../helpers/lemmy"; import { PageContext } from "../auth/PageContext"; import { blockUser } from "./userSlice"; -import { useIonToast } from "@ionic/react"; import { buildBlocked, problemBlockingUser } from "../../helpers/toastMessages"; +import useAppToast from "../../helpers/useAppToast"; export function useUserDetails(handle: string) { const blocks = useAppSelector( @@ -18,7 +18,7 @@ export function useUserDetails(handle: string) { const user = userByHandle[handle]; const { presentLoginIfNeeded } = useContext(PageContext); const dispatch = useAppDispatch(); - const [present] = useIonToast(); + const presentToast = useAppToast(); async function blockOrUnblock() { if (presentLoginIfNeeded()) return; @@ -27,12 +27,12 @@ export function useUserDetails(handle: string) { try { await dispatch(blockUser(!isBlocked, user.id)); } catch (error) { - present(problemBlockingUser); + presentToast(problemBlockingUser); throw error; } - present(buildBlocked(!isBlocked, handle)); + presentToast(buildBlocked(!isBlocked, handle)); } return { isBlocked, user, blockOrUnblock }; diff --git a/src/helpers/toast.ts b/src/helpers/toast.ts new file mode 100644 index 0000000000..55e671ff0e --- /dev/null +++ b/src/helpers/toast.ts @@ -0,0 +1,29 @@ +import { ToastOptions } from "@ionic/core"; + +export function baseToastOptions( + position: "top" | "bottom", + presentAlongsideAppBars = true, +): ToastOptions { + const BASE = { + duration: 3500, + }; + + switch (position) { + case "bottom": + return { + ...BASE, + position: "bottom", + positionAnchor: presentAlongsideAppBars + ? document.querySelector("ion-tab-bar") ?? undefined + : undefined, + }; + case "top": + return { + ...BASE, + position: "top", + positionAnchor: presentAlongsideAppBars + ? document.querySelector("ion-header") ?? undefined + : undefined, + }; + } +} diff --git a/src/helpers/toastMessages.ts b/src/helpers/toastMessages.ts index aa61ef3168..41070f7b73 100644 --- a/src/helpers/toastMessages.ts +++ b/src/helpers/toastMessages.ts @@ -1,45 +1,36 @@ -import { ToastOptions } from "@ionic/core"; +import { AppToastOptions } from "./useAppToast"; -export const voteError: ToastOptions = { +export const voteError: AppToastOptions = { message: "Problem voting. Please try again.", - duration: 3500, - position: "bottom", color: "danger", }; -export const downvotesDisabled: ToastOptions = { +export const downvotesDisabled: AppToastOptions = { message: "Downvotes have been disabled by your server admins.", - duration: 3500, - position: "bottom", color: "warning", }; -export const saveError: ToastOptions = { +export const saveError: AppToastOptions = { message: "Problem bookmarking. Please try again.", - duration: 3500, - position: "bottom", color: "danger", }; -export const allNSFWHidden: ToastOptions = { +export const allNSFWHidden: AppToastOptions = { message: "All NSFW content is now hidden for your account.", - duration: 3500, - position: "bottom", color: "success", }; -export const problemBlockingUser: ToastOptions = { +export const problemBlockingUser: AppToastOptions = { message: "Problem blocking user. Please try again.", - duration: 3500, - position: "bottom", color: "danger", }; -export function buildBlocked(blocked: boolean, handle: string): ToastOptions { +export function buildBlocked( + blocked: boolean, + handle: string, +): AppToastOptions { return { message: `${handle} has been ${blocked ? "blocked" : "unblocked"}`, - duration: 3500, - position: "bottom", color: "success", }; } @@ -47,13 +38,11 @@ export function buildBlocked(blocked: boolean, handle: string): ToastOptions { export function buildProblemSubscribing( isSubscribed: boolean, community: string, -): ToastOptions { +): AppToastOptions { return { message: `Problem ${ isSubscribed ? "unsubscribing from" : "subscribing to" } c/${community}. Please try again.`, - duration: 3500, - position: "bottom", color: "danger", }; } @@ -61,13 +50,11 @@ export function buildProblemSubscribing( export function buildSuccessSubscribing( isSubscribed: boolean, community: string, -): ToastOptions { +): AppToastOptions { return { message: `${ isSubscribed ? "Unsubscribed from" : "Subscribed to" } c/${community}.`, - duration: 3500, - position: "bottom", color: "success", }; } diff --git a/src/helpers/useAppToast.ts b/src/helpers/useAppToast.ts new file mode 100644 index 0000000000..25f9f07c5c --- /dev/null +++ b/src/helpers/useAppToast.ts @@ -0,0 +1,47 @@ +import { useIonToast } from "@ionic/react"; +import { baseToastOptions } from "./toast"; +import useHapticFeedback from "./useHapticFeedback"; +import { isNative } from "./device"; +import { NotificationType } from "@capacitor/haptics"; +import { useCallback } from "react"; + +export interface AppToastOptions { + message: string; + color?: Color; + position?: "top" | "bottom"; + fullscreen?: boolean; +} + +type Color = "success" | "warning" | "danger" | "primary"; + +export default function useAppToast() { + const [present] = useIonToast(); + const vibrate = useHapticFeedback(); + + return useCallback( + (options: AppToastOptions) => { + if (isNative()) + vibrate({ + type: (() => { + switch (options.color) { + case "primary": + case undefined: + case "success": + return NotificationType.Success; + case "warning": + return NotificationType.Warning; + case "danger": + return NotificationType.Error; + } + })(), + }); + + present({ + message: options.message, + color: options.color ?? "primary", + ...baseToastOptions(options.position ?? "bottom", !options.fullscreen), + }); + }, + [present, vibrate], + ); +} diff --git a/src/helpers/useHapticFeedback.ts b/src/helpers/useHapticFeedback.ts index bfa893dca6..6330d64789 100644 --- a/src/helpers/useHapticFeedback.ts +++ b/src/helpers/useHapticFeedback.ts @@ -1,4 +1,8 @@ -import { Haptics, ImpactOptions } from "@capacitor/haptics"; +import { + Haptics, + ImpactOptions, + NotificationOptions, +} from "@capacitor/haptics"; import { useCallback } from "react"; import { useAppSelector } from "../store"; @@ -8,10 +12,11 @@ export default function useHapticFeedback() { ); return useCallback( - (options: ImpactOptions) => { + (options: ImpactOptions | NotificationOptions) => { if (!enabled) return; - Haptics.impact(options); + if ("style" in options) Haptics.impact(options); + else Haptics.notification(options); }, [enabled], ); diff --git a/src/pages/inbox/ComposeButton.tsx b/src/pages/inbox/ComposeButton.tsx index 3b0277844c..76f22700a5 100644 --- a/src/pages/inbox/ComposeButton.tsx +++ b/src/pages/inbox/ComposeButton.tsx @@ -4,20 +4,20 @@ import { IonIcon, IonLoading, useIonRouter, - useIonToast, } from "@ionic/react"; import { createOutline } from "ionicons/icons"; import { useState } from "react"; import { useAppDispatch } from "../../store"; import { getUser } from "../../features/user/userSlice"; import { getHandle } from "../../helpers/lemmy"; +import useAppToast from "../../helpers/useAppToast"; export default function ComposeButton() { const [loading, setLoading] = useState(false); const [isAlertOpen, setIsAlertOpen] = useState(false); const router = useIonRouter(); const dispatch = useAppDispatch(); - const [present] = useIonToast(); + const presentToast = useAppToast(); async function composeNew(handle: string) { setLoading(true); @@ -27,13 +27,11 @@ export default function ComposeButton() { try { user = await dispatch(getUser(handle)); } catch (error) { - present({ + presentToast({ message: error === "couldnt_find_that_username_or_email" ? `Could not find user with handle ${handle}` : "Server error. Please try again.", - duration: 3500, - position: "bottom", color: "danger", }); diff --git a/src/pages/inbox/ConversationPage.tsx b/src/pages/inbox/ConversationPage.tsx index 66858ecb2c..d89a71fe85 100644 --- a/src/pages/inbox/ConversationPage.tsx +++ b/src/pages/inbox/ConversationPage.tsx @@ -8,7 +8,6 @@ import { IonPage, IonTitle, IonToolbar, - useIonToast, useIonViewWillEnter, } from "@ionic/react"; import { useAppDispatch, useAppSelector } from "../../store"; @@ -44,6 +43,7 @@ import { StyledLink } from "../../features/labels/links/shared"; import { useBuildGeneralBrowseLink } from "../../helpers/routes"; import ConversationsMoreActions from "../../features/feed/ConversationsMoreActions"; import { TabContext } from "../../TabContext"; +import useAppToast from "../../helpers/useAppToast"; const MaxSizeContainer = styled(MaxWidthContainer)` height: 100%; @@ -136,7 +136,7 @@ export default function ConversationPage() { const client = useClient(); const userByHandle = useAppSelector((state) => state.user.userByHandle); const [loading, setLoading] = useState(false); - const [present] = useIonToast(); + const presentToast = useAppToast(); const contentRef = useRef["target"]>(null); const buildGeneralBrowseLink = useBuildGeneralBrowseLink(); @@ -193,10 +193,8 @@ export default function ConversationPage() { auth: jwt, }); } catch (error) { - present({ + presentToast({ message: `Message failed to send. Please try again`, - duration: 3500, - position: "bottom", color: "danger", }); setLoading(false); diff --git a/src/pages/settings/RedditDataMigratePage.tsx b/src/pages/settings/RedditDataMigratePage.tsx index 84756b14f4..e8b34c5d07 100644 --- a/src/pages/settings/RedditDataMigratePage.tsx +++ b/src/pages/settings/RedditDataMigratePage.tsx @@ -9,15 +9,15 @@ import { IonPage, IonTitle, IonToolbar, - useIonToast, } from "@ionic/react"; import AppContent from "../../features/shared/AppContent"; import { useEffect, useState } from "react"; import { InsetIonItem } from "../profile/ProfileFeedItemsPage"; import { isValidUrl } from "../../helpers/url"; +import useAppToast from "../../helpers/useAppToast"; export default function RedditMigratePage() { - const [present] = useIonToast(); + const presentToast = useAppToast(); const [subs, setSubs] = useState(); const [link, setLink] = useState(""); @@ -27,19 +27,17 @@ export default function RedditMigratePage() { const subs = parseSubsFromLink(link); if (!subs.length) { - present({ + presentToast({ message: "Problem parsing link. Please make sure the link you entered is correct.", - duration: 3500, - position: "bottom", - color: "danger", + color: "warning", }); setLink(""); return; } setSubs(subs); - }, [link, present]); + }, [link, presentToast]); function renderUpload() { return ( From a87fb942c5b9d8a6327ee8ff4c99e0a075fb7a5a Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:46:23 -0500 Subject: [PATCH 2/2] Upgrade remaining ionic --- package.json | 4 ++-- pnpm-lock.yaml | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index d7ea7600e1..f1c54d6cd6 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "@emotion/styled": "^11.11.0", "@github/markdown-toolbar-element": "^2.2.1", "@ionic/core": "npm:voyager-ionic-core@^7.5.0", - "@ionic/react": "^7.4.3", - "@ionic/react-router": "^7.4.3", + "@ionic/react": "^7.5.0", + "@ionic/react-router": "^7.5.0", "@reduxjs/toolkit": "^1.9.7", "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d7bcc95e8..2b8ae0f925 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,11 +80,11 @@ devDependencies: specifier: npm:voyager-ionic-core@^7.5.0 version: /voyager-ionic-core@7.5.0 '@ionic/react': - specifier: ^7.4.3 - version: 7.4.3(react-dom@18.2.0)(react@18.2.0) + specifier: ^7.5.0 + version: 7.5.0(react-dom@18.2.0)(react@18.2.0) '@ionic/react-router': - specifier: ^7.4.3 - version: 7.4.3(react-dom@18.2.0)(react-router-dom@5.3.4)(react-router@5.3.4)(react@18.2.0) + specifier: ^7.5.0 + version: 7.5.0(react-dom@18.2.0)(react-router-dom@5.3.4)(react-router@5.3.4)(react@18.2.0) '@reduxjs/toolkit': specifier: ^1.9.7 version: 1.9.7(react-redux@8.1.3)(react@18.2.0) @@ -2059,15 +2059,15 @@ packages: - supports-color dev: true - /@ionic/react-router@7.4.3(react-dom@18.2.0)(react-router-dom@5.3.4)(react-router@5.3.4)(react@18.2.0): - resolution: {integrity: sha512-ixQE30XQF409BkgdQGmcEPHvc0K8zuj9v7XBNKMEDyhsohpRSladO4K6w+lusLheT0SdtUqZod8n9KuJ/sXFFA==} + /@ionic/react-router@7.5.0(react-dom@18.2.0)(react-router-dom@5.3.4)(react-router@5.3.4)(react@18.2.0): + resolution: {integrity: sha512-/lOyxQIMIAY1eQgJ0x+NGOgyVpKJW57WMge9uSwduumSutUrPENPRF2c6BqPLxowwHVX0alKm8xy69ON2q+xug==} peerDependencies: react: '>=16.8.6' react-dom: '>=16.8.6' react-router: ^5.0.1 react-router-dom: ^5.0.1 dependencies: - '@ionic/react': 7.4.3(react-dom@18.2.0)(react@18.2.0) + '@ionic/react': 7.5.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-router: 5.3.4(react@18.2.0) @@ -2075,14 +2075,14 @@ packages: tslib: 2.6.2 dev: true - /@ionic/react@7.4.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-j33s8CFe3Cu3AQtIlZdI/W4+e5hDzjRcX6uwqRrizcMQS66Sj9Ik9RN5v3jV/9R8MHLElXZof/AhofNEhe7BTw==} + /@ionic/react@7.5.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-hNpaKHrEtCKKbobV8Caf6aQB1rECNz7VBeCvfyH45UXkiP3EGjtZO8rkG43ej5KT+tNA8y4iJOw43A0C3sfB6Q==} peerDependencies: react: '>=16.8.6' react-dom: '>=16.8.6' dependencies: '@ionic/core': /voyager-ionic-core@7.5.0 - ionicons: 7.1.2 + ionicons: 7.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) tslib: 2.6.2