From 686ce5b504e40646a0d7b92114ef4f07f1f8992a Mon Sep 17 00:00:00 2001
From: Henry Bley-Vroman <bley.vroman@gmail.com>
Date: Thu, 26 Sep 2024 19:31:01 -1000
Subject: [PATCH] Privacy mode: instead of blurring, use an illegible font
 (#3376) (#3377)

* Privacy mode: instead of blurring, use an illegible font (#3376)

* Privacy mode: no layout shift when switching mode

* fix(Spending): update for latest PrivacyFilter

* refactor(PrivacyFilter): use CSS :hover

* chore(PrivacyFilter): no privacy mode BudgetCell height regression...

https://github.com/actualbudget/actual/pull/3377?show_full=true#issuecomment-2357385371

* chore(GroupMonth): drop no-impact PrivacyFilter styles
---
 packages/desktop-client/package.json          |  1 +
 .../src/components/PrivacyFilter.tsx          | 86 ++++++++++++-------
 .../envelope/EnvelopeBudgetComponents.tsx     | 10 ---
 .../envelope/budgetsummary/ToBudgetAmount.tsx |  6 +-
 .../tracking/TrackingBudgetComponents.tsx     |  5 --
 .../budget/tracking/budgetsummary/Saved.tsx   |  4 +-
 .../src/components/reports/ReportSummary.tsx  |  6 +-
 .../reports/reports/CustomReport.tsx          |  2 +-
 .../components/reports/reports/NetWorth.jsx   |  4 +-
 .../components/reports/reports/Spending.tsx   |  8 +-
 .../desktop-client/src/components/table.tsx   |  5 --
 packages/desktop-client/src/fonts.scss        |  2 +
 upcoming-release-notes/3377.md                |  6 ++
 yarn.lock                                     |  8 ++
 14 files changed, 85 insertions(+), 68 deletions(-)
 create mode 100644 upcoming-release-notes/3377.md

diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json
index ffb902de6..b160ffc21 100644
--- a/packages/desktop-client/package.json
+++ b/packages/desktop-client/package.json
@@ -6,6 +6,7 @@
     "build"
   ],
   "devDependencies": {
+    "@fontsource/redacted-script": "^5.0.21",
     "@juggle/resize-observer": "^3.4.0",
     "@playwright/test": "1.41.1",
     "@rollup/plugin-inject": "^5.0.5",
diff --git a/packages/desktop-client/src/components/PrivacyFilter.tsx b/packages/desktop-client/src/components/PrivacyFilter.tsx
index 5a455dc60..f8d832491 100644
--- a/packages/desktop-client/src/components/PrivacyFilter.tsx
+++ b/packages/desktop-client/src/components/PrivacyFilter.tsx
@@ -1,12 +1,12 @@
 // @ts-strict-ignore
 import React, {
-  useState,
-  useCallback,
   Children,
   type ComponentPropsWithRef,
   type ReactNode,
 } from 'react';
 
+import { css } from 'glamor';
+
 import { usePrivacyMode } from '../hooks/usePrivacyMode';
 import { useResponsive } from '../ResponsiveProvider';
 
@@ -44,11 +44,9 @@ export function ConditionalPrivacyFilter({
 
 type PrivacyFilterProps = ComponentPropsWithRef<typeof View> & {
   activationFilters?: (boolean | (() => boolean))[];
-  blurIntensity?: number;
 };
 export function PrivacyFilter({
   activationFilters,
-  blurIntensity,
   children,
   ...props
 }: PrivacyFilterProps) {
@@ -63,46 +61,70 @@ export function PrivacyFilter({
         typeof value === 'boolean' ? value : value(),
       ));
 
-  const blurAmount = blurIntensity != null ? `${blurIntensity}px` : '3px';
-
   return !activate ? (
     <>{Children.toArray(children)}</>
   ) : (
-    <BlurredOverlay blurIntensity={blurAmount} {...props}>
-      {children}
-    </BlurredOverlay>
+    <PrivacyOverlay {...props}>{children}</PrivacyOverlay>
   );
 }
 
-function BlurredOverlay({ blurIntensity, children, ...props }) {
-  const [hovered, setHovered] = useState(false);
-  const onHover = useCallback(() => setHovered(true), [setHovered]);
-  const onHoverEnd = useCallback(() => setHovered(false), [setHovered]);
-
-  const blurStyle = {
-    ...(!hovered && {
-      filter: `blur(${blurIntensity})`,
-      WebkitFilter: `blur(${blurIntensity})`,
-      // To fix blur performance issue in Safari.
-      // https://graffino.com/til/CjT2jrcLHP-how-to-fix-filter-blur-performance-issue-in-safari
-      transform: `translate3d(0, 0, 0)`,
-    }),
-  };
-
+function PrivacyOverlay({ children, ...props }) {
   const { style, ...restProps } = props;
 
   return (
     <View
-      style={{
-        display: style?.display ? style.display : 'inline-flex',
-        ...blurStyle,
-        ...style,
-      }}
-      onPointerEnter={onHover}
-      onPointerLeave={onHoverEnd}
+      className={`${css(
+        [
+          {
+            display: 'inline-flex',
+            flexGrow: 1,
+            position: 'relative',
+            ' > div:first-child': {
+              opacity: 0,
+            },
+            ' > div:nth-child(2)': {
+              display: 'flex',
+            },
+            '&:hover': {
+              ' > div:first-child': {
+                opacity: 1,
+              },
+              ' > div:nth-child(2)': {
+                display: 'none',
+              },
+            },
+          },
+        ],
+        style,
+      )}`}
       {...restProps}
     >
-      {children}
+      <div
+        className={`${css([
+          {
+            display: 'flex',
+            flexGrow: 1,
+          },
+        ])}`}
+      >
+        {children}
+      </div>
+
+      <div
+        aria-hidden="true"
+        className={`${css({
+          flexDirection: 'column',
+          fontFamily: 'Redacted Script',
+          height: '100%',
+          inset: 0,
+          justifyContent: 'center',
+          pointerEvents: 'none',
+          position: 'absolute',
+          width: '100%',
+        })}`}
+      >
+        {children}
+      </div>
     </View>
   );
 }
diff --git a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx
index 68599d33a..449c13a4a 100644
--- a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx
+++ b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx
@@ -169,11 +169,6 @@ export const ExpenseGroupMonth = memo(function ExpenseGroupMonth({
         valueProps={{
           binding: envelopeBudget.groupBalance(id),
           type: 'financial',
-          privacyFilter: {
-            style: {
-              paddingRight: styles.monthRightPadding,
-            },
-          },
         }}
       />
     </View>
@@ -431,11 +426,6 @@ export function IncomeGroupMonth({ month }: IncomeGroupMonthProps) {
         valueProps={{
           binding: envelopeBudget.groupIncomeReceived,
           type: 'financial',
-          privacyFilter: {
-            style: {
-              paddingRight: styles.monthRightPadding,
-            },
-          },
         }}
       />
     </View>
diff --git a/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetAmount.tsx b/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetAmount.tsx
index a03f2560d..04dafd37e 100644
--- a/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetAmount.tsx
+++ b/packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudgetAmount.tsx
@@ -64,7 +64,11 @@ export function ToBudgetAmount({
           offset={3}
           triggerProps={{ isDisabled: isTotalsListTooltipDisabled }}
         >
-          <PrivacyFilter blurIntensity={7}>
+          <PrivacyFilter
+            style={{
+              textAlign: 'center',
+            }}
+          >
             <Block
               onClick={onClick}
               data-cellname={sheetName}
diff --git a/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx b/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx
index c241bf876..b9bb66eda 100644
--- a/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx
+++ b/packages/desktop-client/src/components/budget/tracking/TrackingBudgetComponents.tsx
@@ -178,11 +178,6 @@ export const GroupMonth = memo(function GroupMonth({
           valueProps={{
             binding: trackingBudget.groupBalance(id),
             type: 'financial',
-            privacyFilter: {
-              style: {
-                paddingRight: styles.monthRightPadding,
-              },
-            },
           }}
         />
       )}
diff --git a/packages/desktop-client/src/components/budget/tracking/budgetsummary/Saved.tsx b/packages/desktop-client/src/components/budget/tracking/budgetsummary/Saved.tsx
index 5fdb9e1c4..dfadfa943 100644
--- a/packages/desktop-client/src/components/budget/tracking/budgetsummary/Saved.tsx
+++ b/packages/desktop-client/src/components/budget/tracking/budgetsummary/Saved.tsx
@@ -85,9 +85,7 @@ export function Saved({ projected, style }: SavedProps) {
             },
           ])}`}
         >
-          <PrivacyFilter blurIntensity={7}>
-            {format(saved, 'financial')}
-          </PrivacyFilter>
+          <PrivacyFilter>{format(saved, 'financial')}</PrivacyFilter>
         </View>
       </Tooltip>
     </View>
diff --git a/packages/desktop-client/src/components/reports/ReportSummary.tsx b/packages/desktop-client/src/components/reports/ReportSummary.tsx
index 88968e67a..137ea9270 100644
--- a/packages/desktop-client/src/components/reports/ReportSummary.tsx
+++ b/packages/desktop-client/src/components/reports/ReportSummary.tsx
@@ -117,9 +117,7 @@ export function ReportSummary({
             fontWeight: 800,
           }}
         >
-          <PrivacyFilter blurIntensity={7}>
-            {amountToCurrency(data[balanceTypeOp])}
-          </PrivacyFilter>
+          <PrivacyFilter>{amountToCurrency(data[balanceTypeOp])}</PrivacyFilter>
         </Text>
         <Text style={{ fontWeight: 600 }}>For this time period</Text>
       </View>
@@ -154,7 +152,7 @@ export function ReportSummary({
             fontWeight: 800,
           }}
         >
-          <PrivacyFilter blurIntensity={7}>
+          <PrivacyFilter>
             {!isNaN(average) && integerToCurrency(Math.round(average))}
           </PrivacyFilter>
         </Text>
diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.tsx b/packages/desktop-client/src/components/reports/reports/CustomReport.tsx
index 53ebf2056..346983de0 100644
--- a/packages/desktop-client/src/components/reports/reports/CustomReport.tsx
+++ b/packages/desktop-client/src/components/reports/reports/CustomReport.tsx
@@ -804,7 +804,7 @@ export function CustomReport() {
                       left={<Block>{balanceType}:</Block>}
                       right={
                         <Text>
-                          <PrivacyFilter blurIntensity={5}>
+                          <PrivacyFilter>
                             {amountToCurrency(data[balanceTypeOp])}
                           </PrivacyFilter>
                         </Text>
diff --git a/packages/desktop-client/src/components/reports/reports/NetWorth.jsx b/packages/desktop-client/src/components/reports/reports/NetWorth.jsx
index 31a0abace..7f58ab8d0 100644
--- a/packages/desktop-client/src/components/reports/reports/NetWorth.jsx
+++ b/packages/desktop-client/src/components/reports/reports/NetWorth.jsx
@@ -195,9 +195,7 @@ function NetWorthInner({ widget }) {
           <View
             style={{ ...styles.largeText, fontWeight: 400, marginBottom: 5 }}
           >
-            <PrivacyFilter blurIntensity={5}>
-              {integerToCurrency(data.netWorth)}
-            </PrivacyFilter>
+            <PrivacyFilter>{integerToCurrency(data.netWorth)}</PrivacyFilter>
           </View>
           <PrivacyFilter>
             <Change amount={data.totalChange} />
diff --git a/packages/desktop-client/src/components/reports/reports/Spending.tsx b/packages/desktop-client/src/components/reports/reports/Spending.tsx
index f8d44b93d..7efc495e6 100644
--- a/packages/desktop-client/src/components/reports/reports/Spending.tsx
+++ b/packages/desktop-client/src/components/reports/reports/Spending.tsx
@@ -474,7 +474,7 @@ function SpendingInternal({ widget }: SpendingInternalProps) {
                         }
                         right={
                           <Text style={{ fontWeight: 600 }}>
-                            <PrivacyFilter blurIntensity={5}>
+                            <PrivacyFilter>
                               {amountToCurrency(
                                 Math.abs(data.intervalData[todayDay].compare),
                               )}
@@ -494,7 +494,7 @@ function SpendingInternal({ widget }: SpendingInternalProps) {
                         }
                         right={
                           <Text style={{ fontWeight: 600 }}>
-                            <PrivacyFilter blurIntensity={5}>
+                            <PrivacyFilter>
                               {amountToCurrency(
                                 Math.abs(data.intervalData[todayDay].compareTo),
                               )}
@@ -515,7 +515,7 @@ function SpendingInternal({ widget }: SpendingInternalProps) {
                       }
                       right={
                         <Text style={{ fontWeight: 600 }}>
-                          <PrivacyFilter blurIntensity={5}>
+                          <PrivacyFilter>
                             {amountToCurrency(
                               Math.abs(data.intervalData[todayDay].budget),
                             )}
@@ -535,7 +535,7 @@ function SpendingInternal({ widget }: SpendingInternalProps) {
                       }
                       right={
                         <Text style={{ fontWeight: 600 }}>
-                          <PrivacyFilter blurIntensity={5}>
+                          <PrivacyFilter>
                             {amountToCurrency(
                               Math.abs(data.intervalData[todayDay].average),
                             )}
diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx
index 60f38a0ee..655c8f954 100644
--- a/packages/desktop-client/src/components/table.tsx
+++ b/packages/desktop-client/src/components/table.tsx
@@ -197,11 +197,6 @@ export function Cell({
         privacyFilter={mergeConditionalPrivacyFilterProps(
           {
             activationFilters: [!focused, !exposed],
-            style: {
-              position: 'absolute',
-              width: '100%',
-              height: '100%',
-            },
           },
           privacyFilter,
         )}
diff --git a/packages/desktop-client/src/fonts.scss b/packages/desktop-client/src/fonts.scss
index dba3274d4..cd47daca1 100644
--- a/packages/desktop-client/src/fonts.scss
+++ b/packages/desktop-client/src/fonts.scss
@@ -2,3 +2,5 @@
   $inter-font-path: '../../../node_modules/inter-ui/Inter (web)'
 );
 @include variable.default;
+
+@import "@fontsource/redacted-script";
diff --git a/upcoming-release-notes/3377.md b/upcoming-release-notes/3377.md
new file mode 100644
index 000000000..2ebd4574f
--- /dev/null
+++ b/upcoming-release-notes/3377.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [olets]
+---
+
+Privacy mode: instead of blurring, use an illegible font (#3376)
diff --git a/yarn.lock b/yarn.lock
index 09207fbfe..11aae8c77 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -59,6 +59,7 @@ __metadata:
   version: 0.0.0-use.local
   resolution: "@actual-app/web@workspace:packages/desktop-client"
   dependencies:
+    "@fontsource/redacted-script": "npm:^5.0.21"
     "@juggle/resize-observer": "npm:^3.4.0"
     "@playwright/test": "npm:1.41.1"
     "@rollup/plugin-inject": "npm:^5.0.5"
@@ -1846,6 +1847,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@fontsource/redacted-script@npm:^5.0.21":
+  version: 5.0.21
+  resolution: "@fontsource/redacted-script@npm:5.0.21"
+  checksum: 10/93f506c9e8df827ab1872d433a09079c592f3a20a1c36b9a168e68377967d3d1d282fffd51bee973c2ecb253316ff4641d244f8f05242e4aa968a1eb14d065eb
+  languageName: node
+  linkType: hard
+
 "@formatjs/ecma402-abstract@npm:1.15.0":
   version: 1.15.0
   resolution: "@formatjs/ecma402-abstract@npm:1.15.0"
-- 
GitLab