feat(ios): upgrade button in setting (#13645)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Added a Subscription section in Mobile Settings (for signed-in users)
with plan info and an Upgrade button that opens the native paywall.
  - Supports showing “Pro” and “AI” paywalls.
  - Integrated native paywall provider on iOS.

- Style
- Introduced new styling for the subscription card, content, and button.

- Localization
- Added English strings for subscription title, description, and button.

- Chores
- Minor iOS project cleanup and internal wiring to enable the paywall
module.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
EYHN
2025-09-26 14:27:45 +08:00
committed by GitHub
parent 3f9d9fef63
commit 54498df247
14 changed files with 161 additions and 12 deletions

View File

@@ -12,6 +12,7 @@ import { AppearanceGroup } from './appearance';
import { ExperimentalFeatureSetting } from './experimental';
import { OthersGroup } from './others';
import * as styles from './style.css';
import { UserSubscription } from './subscription';
import { SwipeDialog } from './swipe-dialog';
import { UserProfile } from './user-profile';
import { UserUsage } from './user-usage';
@@ -23,6 +24,7 @@ const MobileSetting = () => {
return (
<div className={styles.root}>
<UserProfile />
<UserSubscription />
<UserUsage />
<AppearanceGroup />
<AboutGroup />

View File

@@ -0,0 +1,56 @@
import { Button } from '@affine/component';
import { AuthService, ServerService } from '@affine/core/modules/cloud';
import { NativePaywallService } from '@affine/core/modules/paywall';
import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
import * as styles from './styles.css';
export const UserSubscription = () => {
const serverService = useService(ServerService);
const authService = useService(AuthService);
const nativePaywallProvider =
useService(NativePaywallService).getNativePaywallProvider();
const t = useI18n();
const supported = useLiveData(
serverService.server.features$.map(f => f.payment)
);
const loggedIn = useLiveData(authService.session.status$) === 'authenticated';
if (!loggedIn) {
return null;
}
if (!supported) {
// TODO: enable this
// return null;
}
if (!nativePaywallProvider) {
return null;
}
return (
<div className={styles.root}>
<div className={styles.content}>
<div className={styles.title}>
{t['com.affine.payment.subscription.title']()}
</div>
<div className={styles.description}>
{t['com.affine.payment.subscription.description']()}
</div>
</div>
<Button
className={styles.button}
variant="primary"
onClick={() =>
void nativePaywallProvider.showPaywall('Pro').catch(console.error)
}
>
{t['com.affine.payment.subscription.button']()}
</Button>
</div>
);
};

View File

@@ -0,0 +1,37 @@
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
export const root = style({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 16,
border: `1px solid ${cssVarV2('database/border')}`,
borderRadius: '12px',
padding: '10px 16px',
backgroundColor: cssVarV2('edgeless/selection/selectionMarqueeBackground'),
});
export const content = style({
display: 'flex',
flexDirection: 'column',
gap: 4,
});
export const title = style({
fontSize: '17px',
lineHeight: '22px',
fontWeight: 600,
color: cssVarV2('text/primary'),
});
export const description = style({
fontSize: '13px',
lineHeight: '18px',
fontWeight: 400,
color: cssVarV2('text/secondary'),
});
export const button = style({
fontSize: '15px',
});

View File

@@ -44,6 +44,7 @@ import { configureNavigationPanelModule } from './navigation-panel';
import { configureNotificationModule } from './notification';
import { configureOpenInApp } from './open-in-app';
import { configureOrganizeModule } from './organize';
import { configurePaywallModule } from './paywall';
import { configurePDFModule } from './pdf';
import { configurePeekViewModule } from './peek-view';
import { configurePermissionsModule } from './permissions';
@@ -130,4 +131,5 @@ export function configureCommonModules(framework: Framework) {
configureIndexerEmbeddingModule(framework);
configureCommentModule(framework);
configureDocSummaryModule(framework);
configurePaywallModule(framework);
}

View File

@@ -0,0 +1,10 @@
import type { Framework } from '@toeverything/infra';
import { NativePaywallService } from './services/native-paywall';
export { NativePaywallProvider } from './providers/native-paywall';
export { NativePaywallService } from './services/native-paywall';
export function configurePaywallModule(framework: Framework) {
framework.service(NativePaywallService);
}

View File

@@ -0,0 +1,9 @@
import { createIdentifier } from '@toeverything/infra';
export interface NativePaywallProvider {
showPaywall(type: 'Pro' | 'AI'): Promise<void>;
}
export const NativePaywallProvider = createIdentifier<NativePaywallProvider>(
'NativePaywallProvider'
);

View File

@@ -0,0 +1,13 @@
import { Service } from '@toeverything/infra';
import { NativePaywallProvider } from '../providers/native-paywall';
export class NativePaywallService extends Service {
constructor() {
super();
}
getNativePaywallProvider() {
return this.framework.getOptional(NativePaywallProvider);
}
}