@oxy.so/services 4.0.4 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/android/src/main/java/so/oxy/identity/OxyIdentityStore.kt +8 -7
  2. package/android/src/main/java/so/oxy/storage/OxyEncryptedPrefs.kt +41 -97
  3. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +5 -3
  4. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyAuthChooser.js +33 -23
  6. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  7. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +9 -0
  8. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -1
  9. package/lib/commonjs/ui/components/authChooser/primitives.js +16 -7
  10. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js +1 -0
  12. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js.map +1 -1
  13. package/lib/module/ui/components/OxyAccountDialogScreen.js +5 -3
  14. package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
  15. package/lib/module/ui/components/OxyAuthChooser.js +33 -23
  16. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  17. package/lib/module/ui/components/authChooser/SignInEntryView.js +9 -0
  18. package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -1
  19. package/lib/module/ui/components/authChooser/primitives.js +16 -7
  20. package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
  21. package/lib/module/ui/hooks/accountDialogSnapshot.js +1 -0
  22. package/lib/module/ui/hooks/accountDialogSnapshot.js.map +1 -1
  23. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +6 -0
  26. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +8 -0
  28. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
  31. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  32. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  33. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  34. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +6 -0
  35. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  36. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +8 -0
  37. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -1
  38. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
  39. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
  40. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/src/ui/components/OxyAccountDialogScreen.tsx +5 -3
  43. package/src/ui/components/OxyAuthChooser.tsx +33 -23
  44. package/src/ui/components/authChooser/SignInEntryView.tsx +13 -0
  45. package/src/ui/components/authChooser/primitives.tsx +20 -8
  46. package/src/ui/hooks/accountDialogSnapshot.ts +1 -0
@@ -169,30 +169,40 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({ onComplete }) => {
169
169
  const handleActivate = useCallback(
170
170
  async (contextId: string) => {
171
171
  if (!controller) return;
172
- // Another switch or removal is already changing this device: the press is
173
- // dropped, not queued — and it is not a failure to report.
174
- if (controller.isDeviceMutationInFlight()) return;
175
- // Already live: activating it again bumps nothing server-side, so treat
176
- // the press as "yes, this one" and close.
177
- if (contextId === controller.getSnapshot().activeContext?.contextId) {
178
- onComplete?.();
179
- return;
180
- }
181
- // The controller's own verdict on the SWITCH — never `snapshot.error`,
182
- // which the directory re-read after a successful switch can also set.
183
- // Reading that as "the switch failed" left the dialog open on a false
184
- // error with the previous account's queries still cached.
185
- const switched = await controller.activateContext(contextId).catch(() => false);
186
- if (!switched) {
187
- toast.error(t('accountSwitcher.toasts.activateFailed'));
188
- return;
172
+ // The controller decides what choosing a row MEANS. Signed in it is a
173
+ // switch; signed out it is "Continue as @handle" — the same sign-in
174
+ // "Continue with Oxy" runs. The signed-out case is why this is not a
175
+ // host-side "already active → close": the device can list an identity
176
+ // (Commons') as active while THIS app holds no session, and closing on it
177
+ // left the app signed out behind a sheet that looked done (#1375 item 20).
178
+ const outcome = await controller.chooseContext(contextId).catch(() => 'failed' as const);
179
+ switch (outcome) {
180
+ // Another switch or removal is already changing this device: the press
181
+ // is dropped, not queued — and it is not a failure to report.
182
+ case 'busy':
183
+ return;
184
+ // The sign-in flow owns the rest: its own view while it runs, and the
185
+ // dialog's close (through `onSignedIn`) when it completes.
186
+ case 'signing-in':
187
+ return;
188
+ // Already live: activating it again bumps nothing server-side, so treat
189
+ // the press as "yes, this one" and close.
190
+ case 'current':
191
+ onComplete?.();
192
+ return;
193
+ // The controller's own verdict on the SWITCH — never `snapshot.error`,
194
+ // which the directory re-read after a successful switch can also set.
195
+ case 'failed':
196
+ toast.error(t('accountSwitcher.toasts.activateFailed'));
197
+ return;
198
+ case 'switched':
199
+ // The subject changed, so every account-scoped query is now about
200
+ // somebody else. The runtime resets its own caches between the bearer
201
+ // commit and the notify; this drops the Query cache the same way.
202
+ queryClient.invalidateQueries();
203
+ onComplete?.();
204
+ return;
189
205
  }
190
- // The subject changed, so every account-scoped query is now about
191
- // somebody else. The runtime resets its own caches between the bearer
192
- // commit and the notify; this drops the Query cache the same way the
193
- // account switch did.
194
- queryClient.invalidateQueries();
195
- onComplete?.();
196
206
  },
197
207
  [controller, onComplete, queryClient, t],
198
208
  );
@@ -13,6 +13,12 @@
13
13
  *
14
14
  * Existing accounts still render ABOVE the CTA: "continue as one of these" is a
15
15
  * choice of WHO, not of HOW.
16
+ *
17
+ * Signed in (the "Add another account" entry), a row is an account to switch
18
+ * to, and the current one carries its check. Signed out, nothing here is
19
+ * current: the device may still list an identity it holds (Commons' shared
20
+ * one), but THIS app has no session, so each row reads "Continue as @handle"
21
+ * and choosing it signs in (OxyHQ/oxy#1375 items 20 and 21).
16
22
  */
17
23
 
18
24
  import type React from 'react';
@@ -90,6 +96,13 @@ const SignInEntryView: React.FC<SignInEntryViewProps> = ({
90
96
  key={context.contextId}
91
97
  context={context}
92
98
  operatedBy={operatedBy}
99
+ continueAsLabel={
100
+ snapshot.hasSession
101
+ ? null
102
+ : t('signin.chooser.continueAs', {
103
+ name: context.handle ? `@${context.handle}` : context.displayName,
104
+ })
105
+ }
93
106
  theme={theme}
94
107
  activating={snapshot.activatingContextId === context.contextId}
95
108
  disabled={snapshot.activatingContextId !== null}
@@ -96,19 +96,31 @@ export const Dividerish: React.FC<{ theme: Theme; label: string }> = ({ theme, l
96
96
  * directory), not from the theme and not from whoever is signed in. This surface
97
97
  * is where a device holding two people is most visible, and it is reached while
98
98
  * signed out — so there is no "current user" whose colour could stand in.
99
+ *
100
+ * `continueAsLabel` is the signed-out form. The device can list an identity
101
+ * (and even mark it active) while this app holds no session, so a check there
102
+ * would claim a sign-in that has not happened. Given a label, the row never
103
+ * reads as current: its first line is "Continue as @handle", the account's
104
+ * name moves to the second line, and it ends in a chevron like any other row.
99
105
  */
100
106
  export const AccountRow: React.FC<{
101
107
  context: SwitcherContextRow;
102
108
  /** The person this account is reached through, when that is not obvious. */
103
109
  operatedBy: string | null;
110
+ /** Signed out: the row's "Continue as @handle" line. `null` when signed in. */
111
+ continueAsLabel?: string | null;
104
112
  theme: Theme;
105
113
  activating: boolean;
106
114
  disabled: boolean;
107
115
  onPress: () => void;
108
- }> = ({ context, operatedBy, theme, activating, disabled, onPress }) => {
116
+ }> = ({ context, operatedBy, continueAsLabel = null, theme, activating, disabled, onPress }) => {
109
117
  const accent = resolveAccentHex(context.color, theme.colors.primary);
110
118
  const rowDisabled = disabled || !context.canActivate;
111
- const secondary = operatedBy ?? (context.handle ? `@${context.handle}` : null);
119
+ const current = continueAsLabel === null && context.isActive;
120
+ const primary = continueAsLabel ?? context.displayName;
121
+ const secondary =
122
+ operatedBy ??
123
+ (continueAsLabel !== null ? context.displayName : context.handle ? `@${context.handle}` : null);
112
124
 
113
125
  return (
114
126
  <BloomColorScope colorPreset={toPreset(context.color)} asChild>
@@ -116,7 +128,7 @@ export const AccountRow: React.FC<{
116
128
  style={[
117
129
  styles.accountRow,
118
130
  {
119
- borderColor: context.isActive ? accent : theme.colors.border,
131
+ borderColor: current ? accent : theme.colors.border,
120
132
  backgroundColor: theme.colors.card,
121
133
  },
122
134
  rowDisabled && !activating ? styles.rowDisabled : null,
@@ -124,10 +136,10 @@ export const AccountRow: React.FC<{
124
136
  onPress={onPress}
125
137
  disabled={rowDisabled}
126
138
  accessibilityRole="button"
127
- accessibilityState={{ selected: context.isActive, disabled: rowDisabled }}
128
- accessibilityLabel={context.displayName}
139
+ accessibilityState={{ selected: current, disabled: rowDisabled }}
140
+ accessibilityLabel={primary}
129
141
  >
130
- <View style={[styles.avatarRing, { borderColor: context.isActive ? accent : 'transparent' }]}>
142
+ <View style={[styles.avatarRing, { borderColor: current ? accent : 'transparent' }]}>
131
143
  <Avatar
132
144
  source={context.avatarUrl ?? undefined}
133
145
  variant="thumb"
@@ -137,7 +149,7 @@ export const AccountRow: React.FC<{
137
149
  </View>
138
150
  <View style={styles.rowMeta}>
139
151
  <Text style={[styles.rowName, { color: theme.colors.text }]} numberOfLines={1}>
140
- {context.displayName}
152
+ {primary}
141
153
  </Text>
142
154
  {secondary ? (
143
155
  <Text style={[styles.rowHandle, { color: theme.colors.textSecondary }]} numberOfLines={1}>
@@ -147,7 +159,7 @@ export const AccountRow: React.FC<{
147
159
  </View>
148
160
  {activating ? (
149
161
  <MaterialCommunityIcons name="loading" size={20} color={accent} />
150
- ) : context.isActive ? (
162
+ ) : current ? (
151
163
  <MaterialCommunityIcons name="check-circle" size={20} color={accent} />
152
164
  ) : (
153
165
  <MaterialCommunityIcons name="chevron-right" size={20} color={theme.colors.textSecondary} />
@@ -15,6 +15,7 @@ import type { AccountDialogSnapshot } from '@oxy.so/core';
15
15
  export const EMPTY_ACCOUNT_DIALOG_SNAPSHOT: AccountDialogSnapshot = {
16
16
  view: 'accounts',
17
17
  backView: null,
18
+ hasSession: false,
18
19
  directory: null,
19
20
  activeContext: null,
20
21
  activatingContextId: null,