修复:禁止截屏功能
This commit is contained in:
parent
3edf55cacd
commit
65439a66b2
@ -71,6 +71,7 @@
|
||||
"firstPurchase": "qlw4fp",
|
||||
"purchase": "b2ms4n",
|
||||
"register": "2k7vm5",
|
||||
"paymentFailed": "",
|
||||
"price_599": "mtzzqk",
|
||||
"price_999": "m9ivl1",
|
||||
"price_1999": "kp7a52",
|
||||
@ -80,7 +81,7 @@
|
||||
"extConfig": {
|
||||
"keys": {
|
||||
"showVideoMenu": ["go_run", "need_wait"],
|
||||
"allowScreenshot": ["screen"],
|
||||
"forbidScreenshot": ["screen"],
|
||||
"blockScreenshot": ["safe_area"],
|
||||
"allowThirdPartyPayment": ["san_fang", "lucky"],
|
||||
"privacyUrl": ["privacy"],
|
||||
|
||||
@ -171,7 +171,7 @@ lib/
|
||||
|------|------|
|
||||
| `ExtConfigRuntime.data` | `ValueNotifier<ExtConfigData?>`,监听后刷新首页 Tab / Grid |
|
||||
| `ExtConfigRuntime.commonInfoSucceeded` | `true` / `false` / `null`:是否成功拉到 common_info(**建议仅在为 `true` 时展示核心业务 UI**) |
|
||||
| `ExtConfigData` | `showVideoMenu`、`allowScreenshot`、`allowThirdPartyPayment`、`privacyUrl`、`agreementUrl`、`items` |
|
||||
| `ExtConfigData` | `showVideoMenu`、`forbidScreenshot`、`allowThirdPartyPayment`、`privacyUrl`、`agreementUrl`、`items` |
|
||||
| `ExtConfigItem` | 单项:`image`、`image_fix`、`img_need`、`cost`、`title`、`params` / `detail`;`taskExt` ⇒ `params ?? detail` |
|
||||
| `kExtConfigItemsCategoryId` | 固定 `-1`,作「静态 items Tab」分类 id |
|
||||
| `mergeHomeTabsWithExtConfigItems<T>()` | `showVideoMenu == true` 时在 API Tab 列表 **末尾** 追加静态 Tab |
|
||||
@ -182,7 +182,7 @@ lib/
|
||||
| 语义 | 默认候选键(首个存在则生效) |
|
||||
|------|------------------------------|
|
||||
| 展示顶部 Video Tab 栏 + items 固定最后一格 | `go_run`、`need_wait` |
|
||||
| 允许截屏 | `screen`;若无则看 `safe_area`(`true` ⇒ 不允许截屏) |
|
||||
| 是否禁止截屏(逻辑 [ExtConfigData.forbidScreenshot]) | `screen`;若无则看 `safe_area`(均为 **`true` = 禁止截屏**) |
|
||||
| 允许第三方支付 | `san_fang`、`lucky` |
|
||||
| 隐私 / 协议 URL | `privacy`、`agreement` |
|
||||
| items 数组 | `items` |
|
||||
@ -211,7 +211,7 @@ lib/
|
||||
}
|
||||
```
|
||||
|
||||
首页逻辑建议(对齐 `app_client`):`await FrameworkAuthService.loginComplete` 后判断 `ExtConfigRuntime.commonInfoSucceeded.value == true` 再进入主页;`showVideoMenu == true` 时展示顶部 Tab,分类列表用 `mergeHomeTabsWithExtConfigItems` 把静态 Tab 放在最后,**该 Tab 的 Grid 数据源为 `ExtConfigRuntime.data.value?.items`**。第三方支付入口用 `allowThirdPartyPayment`;截屏策略用 `shouldPreventCapture` 或自行根据 `allowScreenshot` 调用宿主侧防护(框架不强制依赖 `screen_secure`)。
|
||||
首页逻辑建议(对齐 `app_client`):`await FrameworkAuthService.loginComplete` 后判断 `ExtConfigRuntime.commonInfoSucceeded.value == true` 再进入主页;`showVideoMenu == true` 时展示顶部 Tab,分类列表用 `mergeHomeTabsWithExtConfigItems` 把静态 Tab 放在最后,**该 Tab 的 Grid 数据源为 `ExtConfigRuntime.data.value?.items`**。第三方支付入口用 `allowThirdPartyPayment`;截屏策略用 `forbidScreenshot` 驱动宿主侧防护(如 `screen_secure`;框架不强制依赖)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
| 子节 | 说明 |
|
||||
|------|------|
|
||||
| `keys.showVideoMenu` | 字符串数组,如 `["go_run","need_wait"]`,依次为布尔字段候选键 |
|
||||
| `keys.allowScreenshot` | 直接表示「允许截屏」的键,如 `["screen"]` |
|
||||
| `keys.forbidScreenshot` | 线网为 `true` 时表示禁止截屏的键,如 `["screen"]`(兼容旧键名 `allowScreenshot`) |
|
||||
| `keys.blockScreenshot` | 为 `true` 时表示**禁止**截屏的键,如 `["safe_area"]` |
|
||||
| `keys.allowThirdPartyPayment` | 如 `["san_fang","lucky"]` |
|
||||
| `keys.privacyUrl` / `agreementUrl` / `items` | 隐私、协议 URL、items 数组所在键名 |
|
||||
|
||||
41
lib/app.dart
41
lib/app.dart
@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:client_proxy_framework/client_proxy_framework.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
@ -8,15 +10,50 @@ import 'core/theme/app_colors.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
import 'features/shell/main_screen.dart';
|
||||
|
||||
class App extends StatelessWidget {
|
||||
class App extends StatefulWidget {
|
||||
const App({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_reportFacebookActivateApp('first_frame');
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 与 app_client 一致:关闭 FB 自动 App Events 时手动 [FacebookService.activateApp]。
|
||||
void _reportFacebookActivateApp(String reason) {
|
||||
FacebookService.activateApp();
|
||||
if (kDebugMode) {
|
||||
debugPrint('[App] Facebook activateApp ($reason)');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_reportFacebookActivateApp('lifecycle_resumed');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: title,
|
||||
title: widget.title,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: buildFunyMeeTheme(),
|
||||
home: const MainScreen(),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
@ -6,6 +7,7 @@ import 'package:client_proxy_framework/client_proxy_framework.dart';
|
||||
import 'package:crypto/crypto.dart' show md5;
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:screen_secure/screen_secure.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../user/user_state.dart';
|
||||
@ -58,6 +60,11 @@ class AppAuthCallbacks implements AuthServiceCallbacks {
|
||||
avatar: data.avatar,
|
||||
userName: data.userName,
|
||||
);
|
||||
unawaited(
|
||||
AnalyticsEvents.trackRegisterIfNeeded(
|
||||
firstRegister: data.firstRegister == true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -77,11 +84,77 @@ class AppAuthCallbacks implements AuthServiceCallbacks {
|
||||
class AuthService {
|
||||
static final _authCallbacks = AppAuthCallbacks();
|
||||
|
||||
static var _screenSecureListenerBound = false;
|
||||
|
||||
/// [ExtConfigRuntime.data] 初次为 `null` 时触发的 `_applyScreenSecure(null)` 与后续
|
||||
/// common_info 触发的 `_applyScreenSecure(…)` 均为异步:若前者后完成会错误地关闭防护,故用序号丢弃过期任务。
|
||||
static int _screenSecureApplyGeneration = 0;
|
||||
|
||||
static Future<void> init() async {
|
||||
FrameworkAuthService.init(_authCallbacks);
|
||||
_bindScreenSecureToExtConfig();
|
||||
await FrameworkAuthService.start();
|
||||
}
|
||||
|
||||
/// 与 [app_client] `AuthService.runWithNativeMediaPicker` 一致:系统相册/相机返回后若开启防截屏,
|
||||
/// 部分机型会黑屏;选图前后临时关闭防护,结束后再按 [ExtConfigRuntime] 恢复。
|
||||
static Future<T> runWithNativeMediaPicker<T>(Future<T> Function() action) async {
|
||||
if (defaultTargetPlatform != TargetPlatform.android &&
|
||||
defaultTargetPlatform != TargetPlatform.iOS) {
|
||||
return await action();
|
||||
}
|
||||
try {
|
||||
await ScreenSecure.disableScreenshotBlock();
|
||||
await ScreenSecure.disableScreenRecordBlock();
|
||||
} on ScreenSecureException catch (e) {
|
||||
debugPrint('[AuthService] native media picker: disable failed: ${e.message}');
|
||||
}
|
||||
try {
|
||||
return await action();
|
||||
} finally {
|
||||
unawaited(_applyScreenSecure(ExtConfigRuntime.data.value));
|
||||
}
|
||||
}
|
||||
|
||||
static void _bindScreenSecureToExtConfig() {
|
||||
if (_screenSecureListenerBound) return;
|
||||
_screenSecureListenerBound = true;
|
||||
void listener() {
|
||||
unawaited(_applyScreenSecure(ExtConfigRuntime.data.value));
|
||||
}
|
||||
|
||||
ExtConfigRuntime.data.addListener(listener);
|
||||
// 勿在此处同步调用 listener():`data == null` 的 disable 若晚于 common_info 的 enable 完成,会把防截屏关掉。
|
||||
}
|
||||
|
||||
/// [ExtConfigData]:`screen` / `safe_area` 等键在 [ExtConfigKeySchema] 下解析为 [ExtConfigData.forbidScreenshot];
|
||||
/// 为 `true` 时启用系统截屏/录屏防护(与 app_client `safe_area` 语义对齐)。
|
||||
static Future<void> _applyScreenSecure(ExtConfigData? ext) async {
|
||||
if (defaultTargetPlatform != TargetPlatform.android &&
|
||||
defaultTargetPlatform != TargetPlatform.iOS) {
|
||||
return;
|
||||
}
|
||||
final gen = ++_screenSecureApplyGeneration;
|
||||
final block = ext?.forbidScreenshot == true;
|
||||
try {
|
||||
await ScreenSecure.init(screenshotBlock: false, screenRecordBlock: false);
|
||||
if (gen != _screenSecureApplyGeneration) return;
|
||||
if (block) {
|
||||
await ScreenSecure.enableScreenshotBlock();
|
||||
await ScreenSecure.enableScreenRecordBlock();
|
||||
} else {
|
||||
await ScreenSecure.disableScreenshotBlock();
|
||||
await ScreenSecure.disableScreenRecordBlock();
|
||||
}
|
||||
if (gen != _screenSecureApplyGeneration) return;
|
||||
if (kDebugMode && block) {
|
||||
debugPrint('[AuthService] ScreenSecure: enabled (forbidScreenshot)');
|
||||
}
|
||||
} on ScreenSecureException catch (e) {
|
||||
debugPrint('[AuthService] ScreenSecure: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> get loginComplete => FrameworkAuthService.loginComplete;
|
||||
|
||||
/// 登录流程是否已结束(含 fast_login + common_info 链路);用于遮罩,勿用 [loginComplete] 的 Future(首帧可能为 null)。
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
import '../../core/app_env.dart';
|
||||
import '../../core/auth/auth_service.dart';
|
||||
import '../../core/open_purchase_store.dart';
|
||||
import '../../core/user/user_state.dart';
|
||||
import '../../design/pencil_theme.dart';
|
||||
@ -142,7 +143,9 @@ class _GenerateScreenState extends State<GenerateScreen> {
|
||||
if (!mounted) return;
|
||||
final source = await _showPickImageSourceSheet(context);
|
||||
if (source == null || !mounted) return;
|
||||
final x = await _picker.pickImage(source: source, imageQuality: 92);
|
||||
final x = await AuthService.runWithNativeMediaPicker(
|
||||
() => _picker.pickImage(source: source, imageQuality: 92),
|
||||
);
|
||||
if (x == null || !mounted) return;
|
||||
setState(() {
|
||||
if (slot == 0) {
|
||||
@ -619,7 +622,7 @@ class _GenerateScreenState extends State<GenerateScreen> {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Est. cost · $_estimatedCost credits',
|
||||
'cost · $_estimatedCost credits',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 13,
|
||||
@ -627,22 +630,6 @@ class _GenerateScreenState extends State<GenerateScreen> {
|
||||
color: PencilTheme.stone600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () => openPurchaseStore(context),
|
||||
child: Text(
|
||||
'Balance · ${credits.toStringAsFixed(2)}',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
color: PencilTheme.inkSoft,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: PencilTheme.inkSoft.withValues(
|
||||
alpha: 0.45,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
@ -73,7 +73,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 28 +
|
||||
bottom:
|
||||
28 +
|
||||
(widget.isRootTab
|
||||
? PencilTheme.mainTabBottomChromeReserve(context)
|
||||
: 0),
|
||||
@ -154,59 +155,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 12),
|
||||
child: _menuCard(context),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const PurchaseScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'Buy credits',
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: PencilTheme.underlineGold,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await UserAccountRefresh.fetchAndNotify(
|
||||
app: currentBackendAppType(),
|
||||
userId: UserState.userId.value,
|
||||
onAccount: (a) {
|
||||
if (a.credits != null) {
|
||||
UserState.setCredits(a.credits!);
|
||||
}
|
||||
if (a.avatar != null) {
|
||||
UserState.setAvatar(a.avatar!);
|
||||
}
|
||||
if (a.userName != null) {
|
||||
UserState.setUserName(a.userName!);
|
||||
}
|
||||
},
|
||||
onFailure: (m) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text(m)));
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'Refresh',
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: PencilTheme.stone600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
@ -219,8 +168,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
}
|
||||
|
||||
Widget _avatarFallback() {
|
||||
return Icon(Icons.person_rounded,
|
||||
size: 44, color: PencilTheme.profileAvatarIcon);
|
||||
return Icon(
|
||||
Icons.person_rounded,
|
||||
size: 44,
|
||||
color: PencilTheme.profileAvatarIcon,
|
||||
);
|
||||
}
|
||||
|
||||
String _formatCredits(int c) {
|
||||
@ -235,17 +187,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
required String? url,
|
||||
}) {
|
||||
if (url == null || url.trim().isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Link not configured')),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Link not configured')));
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => AppWebViewScreen(
|
||||
title: title,
|
||||
initialUrl: url.trim(),
|
||||
),
|
||||
builder: (_) => AppWebViewScreen(title: title, initialUrl: url.trim()),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -288,17 +237,24 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
_divider(),
|
||||
_row('Version', value: 'v$_version'),
|
||||
_divider(),
|
||||
_row('Delete account',
|
||||
_row(
|
||||
'Delete account',
|
||||
danger: true,
|
||||
trailing: Icons.chevron_right_rounded,
|
||||
onTap: () => _delete(context)),
|
||||
onTap: () => _delete(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _row(String title,
|
||||
{IconData? trailing, String? value, bool danger = false, VoidCallback? onTap}) {
|
||||
Widget _row(
|
||||
String title, {
|
||||
IconData? trailing,
|
||||
String? value,
|
||||
bool danger = false,
|
||||
VoidCallback? onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
title: Text(
|
||||
@ -310,17 +266,21 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
),
|
||||
trailing: value != null
|
||||
? Text(value,
|
||||
? Text(
|
||||
value,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
color: const Color(0xFF78716C)))
|
||||
: Icon(trailing,
|
||||
color: danger ? const Color(0xFFFCA292) : const Color(0xFFA8A29E)),
|
||||
color: const Color(0xFF78716C),
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
trailing,
|
||||
color: danger ? const Color(0xFFFCA292) : const Color(0xFFA8A29E),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _divider() =>
|
||||
Container(height: 1, color: const Color(0xFFF5F5F4));
|
||||
Widget _divider() => Container(height: 1, color: const Color(0xFFF5F5F4));
|
||||
|
||||
Future<void> _delete(BuildContext context) async {
|
||||
final ok = await showDeleteAccountConfirmationFlow(context);
|
||||
@ -335,12 +295,16 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
UserState.clear();
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Account deleted. Please restart the app and sign in again.')),
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Account deleted. Please restart the app and sign in again.',
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(res.msg)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(res.msg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,6 +212,7 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
_paying = false;
|
||||
_selectedIndex = null;
|
||||
});
|
||||
AnalyticsEvents.trackPaymentFailed();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
@ -230,6 +231,7 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
_paying = false;
|
||||
_selectedIndex = null;
|
||||
});
|
||||
AnalyticsEvents.trackPaymentFailed();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('No payment methods available.')),
|
||||
);
|
||||
@ -256,7 +258,7 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
|
||||
final sink = _PurchaseSink(
|
||||
final innerSink = _PurchaseSink(
|
||||
context: context,
|
||||
onRefresh: () {
|
||||
if (mounted) {
|
||||
@ -272,6 +274,10 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
);
|
||||
final sink = PaymentSettlementSinkWithAnalytics(
|
||||
inner: innerSink,
|
||||
analyticsProduct: item,
|
||||
);
|
||||
|
||||
final outcome = await ThirdPartyCheckoutCoordinator.createOrder(
|
||||
userId: uid,
|
||||
@ -288,6 +294,7 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
_paying = false;
|
||||
_selectedIndex = null;
|
||||
});
|
||||
AnalyticsEvents.trackPaymentFailed();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(outcome.message ?? 'Create order failed')),
|
||||
);
|
||||
@ -319,6 +326,7 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
_paying = false;
|
||||
_selectedIndex = null;
|
||||
});
|
||||
AnalyticsEvents.trackPaymentFailed();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Invalid payment response.')),
|
||||
);
|
||||
@ -367,6 +375,8 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
AnalyticsEvents.trackTierSelection(item);
|
||||
|
||||
final ext = ExtConfigRuntime.data.value;
|
||||
if (_useThirdPartyFromExt(ext)) {
|
||||
await _onBuyThirdParty(item, index);
|
||||
@ -389,7 +399,7 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
|
||||
final sink = _PurchaseSink(
|
||||
final innerSink = _PurchaseSink(
|
||||
context: context,
|
||||
onRefresh: () {
|
||||
if (mounted) {
|
||||
@ -403,6 +413,10 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
);
|
||||
final sink = PaymentSettlementSinkWithAnalytics(
|
||||
inner: innerSink,
|
||||
analyticsProduct: item,
|
||||
);
|
||||
|
||||
await NativeIapCoordinator.purchaseGooglePlay(
|
||||
sink: sink,
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
import '../../core/auth/auth_service.dart';
|
||||
import '../../design/pencil_theme.dart';
|
||||
import '../../widgets/pencil_chrome.dart';
|
||||
import 'report_feedback_upload.dart';
|
||||
@ -39,9 +40,11 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||
|
||||
Future<void> _pickImage() async {
|
||||
if (_submitting) return;
|
||||
final x = await _picker.pickImage(
|
||||
final x = await AuthService.runWithNativeMediaPicker(
|
||||
() => _picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
imageQuality: 85,
|
||||
),
|
||||
);
|
||||
if (x == null || !mounted) return;
|
||||
setState(() => _imageFile = File(x.path));
|
||||
|
||||
@ -743,6 +743,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
screen_secure:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: screen_secure
|
||||
sha256: "9660d0a285f7e27d482333779153e3cb17c5fd929e15373e51abad63810df7b4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -29,6 +29,7 @@ dependencies:
|
||||
package_info_plus: ^8.1.2
|
||||
webview_flutter: ^4.13.1
|
||||
gal: ^2.3.2
|
||||
screen_secure: ^1.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user