mirror of
https://github.com/block/buzz.git
synced 2026-11-12 20:31:12 +00:00
Neither mobile app offers a way to reach Builderlab's web-only account features: GitHub linking, billing and creating a community. This adds a **Builderlab account** row to Settings that opens the build's Builderlab origin (app.builderlab.xyz on public builds) in the system browser. The row shows only on public builds. An internal build's Builderlab is its organization's tenant, where billing and the single community are managed by the organization, so the link wouldn't lead anywhere useful. Nostr-only builds have no Builderlab. Sign-in uses a non-ephemeral `ASWebAuthenticationSession`, which shares Safari's cookies, so Safari can reuse the Builderlab web session. A browser other than Safari, or Android, may still ask the user to sign in. | Before | After | | --- | --- | | <img alt="before" src="https://github.com/user-attachments/assets/a8f2157b-5a54-41fc-8937-013638b3347e" width="300"> | <img alt="after" src="https://github.com/user-attachments/assets/f1161f3b-c926-4a8c-b6a1-7532ef72749c" width="300"> | Nothing else gets worse. The change adds one row and one browser launch on tap, and does no work at startup or in the background. A failure stays inside this row: if the browser doesn't open, a toast says so. ### Validation - Added tests: `builderlab_account_row_test.dart` checks that the row opens `HostedEnvironment.origin` and shows a toast when the browser fails to open (public build), and that the row is absent otherwise. It is added to the CI public-build lane. - `settings_page_test.dart`: the version-footer scroll check now finds the footer after it scrolls just offscreen. The extra row pushed it there under public-build defines. - Settings tests pass under both public and Nostr-only defines, and `flutter analyze` is clean. - iOS simulator (public build, signed in to the public Builderlab): the row appears below Connection, and tapping it opens Safari at Builderlab. This simulator's session came from an identity import, not the in-app browser sign-in, so Safari was signed out. I haven't yet verified that Safari arrives signed in after a real in-app sign-in. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@localhost>
573 lines
20 KiB
Dart
573 lines
20 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'dart:ui' as ui;
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:buzz/features/settings/settings_page.dart';
|
|
import 'package:buzz/shared/community/community_membership_provider.dart';
|
|
import 'package:buzz/shared/community/community.dart';
|
|
import 'package:buzz/shared/community/community_provider.dart';
|
|
import 'package:buzz/shared/theme/theme.dart';
|
|
import 'package:buzz/shared/push/dev_push_lease.dart';
|
|
import 'package:buzz/shared/push/push_relay_capability_provider.dart';
|
|
import 'package:buzz/shared/relay/app_lifecycle_provider.dart';
|
|
import 'package:buzz/shared/widgets/app_list.dart';
|
|
import 'package:buzz/shared/widgets/app_list_card.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
void main() {
|
|
setUp(() {
|
|
PackageInfo.setMockInitialValues(
|
|
appName: 'Buzz',
|
|
packageName: 'xyz.block.buzz',
|
|
version: '0.16.0',
|
|
buildNumber: '432',
|
|
buildSignature: '',
|
|
);
|
|
});
|
|
|
|
for (final buildNumber in ['432', '', '2147483647']) {
|
|
testWidgets('shows version with build number "$buildNumber"', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(320, 700);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
PackageInfo.setMockInitialValues(
|
|
appName: 'Buzz',
|
|
packageName: 'xyz.block.buzz',
|
|
version: '0.16.0',
|
|
buildNumber: buildNumber,
|
|
buildSignature: '',
|
|
);
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
currentCommunityRoleProvider.overrideWithValue(
|
|
const AsyncData<CommunityMemberRole?>(CommunityMemberRole.admin),
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
builder: (context, child) => MediaQuery(
|
|
data: MediaQuery.of(
|
|
context,
|
|
).copyWith(textScaler: TextScaler.linear(2)),
|
|
child: child!,
|
|
),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(find.text('Invite to community'), findsNothing);
|
|
await tester.pumpAndSettle();
|
|
final label = buildNumber.isEmpty ? 'v0.16.0' : 'v0.16.0 ($buildNumber)';
|
|
final version = find.text(label);
|
|
await tester.scrollUntilVisible(version, 200);
|
|
await tester.pumpAndSettle();
|
|
expect(version.hitTestable(), findsOneWidget);
|
|
expect(
|
|
find.ancestor(of: version, matching: find.byType(ListView)),
|
|
findsOneWidget,
|
|
);
|
|
final beforeScroll = tester.getTopLeft(version).dy;
|
|
await tester.drag(find.byType(ListView), const Offset(0, 100));
|
|
await tester.pumpAndSettle();
|
|
// Dragging down can push the footer just below the viewport.
|
|
expect(
|
|
tester.getTopLeft(find.text(label, skipOffstage: false)).dy,
|
|
greaterThan(beforeScroll),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
|
|
for (final viaKeyboard in [false, true]) {
|
|
testWidgets(
|
|
'${viaKeyboard ? 'keyboard activation of' : 'tapping'} the version copies the whole version string',
|
|
(tester) async {
|
|
String? copied;
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
(call) async {
|
|
if (call.method == 'Clipboard.setData') {
|
|
copied = (call.arguments as Map)['text'] as String?;
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
null,
|
|
),
|
|
);
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [savedPrefsProvider.overrideWithValue(prefs)],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final version = find.text('v0.16.0 (432)');
|
|
await tester.scrollUntilVisible(version, 200);
|
|
await tester.pumpAndSettle();
|
|
final target = find.ancestor(
|
|
of: version,
|
|
matching: find.byType(InkWell),
|
|
);
|
|
expect(
|
|
tester.getSize(target).height,
|
|
greaterThanOrEqualTo(kMinInteractiveDimension),
|
|
);
|
|
|
|
if (viaKeyboard) {
|
|
final versionFocus = Focus.of(tester.element(version));
|
|
var tabs = 0;
|
|
while (!versionFocus.hasPrimaryFocus) {
|
|
expect(
|
|
tabs++,
|
|
lessThan(50),
|
|
reason: 'version is not reachable by Tab',
|
|
);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
|
await tester.pump();
|
|
}
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
} else {
|
|
await tester.tap(version);
|
|
}
|
|
await tester.pump();
|
|
|
|
expect(copied, 'v0.16.0 (432)');
|
|
expect(find.text('Version copied'), findsOneWidget);
|
|
await tester.pump(const Duration(seconds: 5));
|
|
await tester.pumpAndSettle();
|
|
},
|
|
);
|
|
}
|
|
|
|
for (final outcome in ['absent', 'error']) {
|
|
testWidgets('hides notifications while capability loads and is $outcome', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final capability = Completer<BuzzPushLeaseDescriptor?>();
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
activeCommunityProvider.overrideWith(
|
|
(ref) async => Community.create(
|
|
name: 'No push',
|
|
relayUrl: 'wss://relay.example',
|
|
).copyWith(pushNotificationsEnabled: false),
|
|
),
|
|
currentRelayPushDescriptorProvider.overrideWith(
|
|
(ref) => capability.future,
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Notifications'), findsNothing);
|
|
expect(find.byType(Switch), findsNothing);
|
|
if (outcome == 'absent') {
|
|
capability.complete(null);
|
|
} else {
|
|
capability.completeError(StateError('discovery failed'));
|
|
}
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Notifications'), findsNothing);
|
|
expect(find.text('Push notifications'), findsNothing);
|
|
expect(find.byType(Switch), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
}
|
|
|
|
testWidgets('shows the persisted per-community push opt-in on iOS', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final community = Community.create(
|
|
name: 'Team',
|
|
relayUrl: 'wss://relay.example',
|
|
).copyWith(pushNotificationsEnabled: true);
|
|
|
|
final capability = Future<BuzzPushLeaseDescriptor?>.value(_pushDescriptor);
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
activeCommunityProvider.overrideWith((ref) async => community),
|
|
currentRelayPushDescriptorProvider.overrideWith((ref) => capability),
|
|
appLifecycleProvider.overrideWith(_SettingsLifecycleNotifier.new),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
find.byKey(const ValueKey('push-notifications-enabled')),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.widget<Switch>(find.byType(Switch)).value, isTrue);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
for (final brightness in Brightness.values) {
|
|
testWidgets(
|
|
'shows status above profile editing and routes photo directly in ${brightness.name}',
|
|
(tester) async {
|
|
if (Platform.environment.containsKey('PROFILE_SCREENSHOTS')) {
|
|
await tester.runAsync(() async {
|
|
for (final font in {
|
|
'Inter': 'assets/fonts/InterVariable.ttf',
|
|
'BuzzTabler': 'assets/fonts/TablerIcons.ttf',
|
|
}.entries) {
|
|
await (FontLoader(
|
|
font.key,
|
|
)..addFont(rootBundle.load(font.value))).load();
|
|
}
|
|
});
|
|
}
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [savedPrefsProvider.overrideWithValue(prefs)],
|
|
child: MaterialApp(
|
|
theme: brightness == Brightness.dark
|
|
? AppTheme.dark()
|
|
: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.square(dimension: 128),
|
|
profileEditPageBuilder: (_) =>
|
|
const Scaffold(body: Text('Profile editor destination')),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Profile'), findsNothing);
|
|
expect(find.text('Edit profile'), findsNothing);
|
|
expect(find.text('Display name'), findsOneWidget);
|
|
expect(find.text('Profile description'), findsOneWidget);
|
|
expect(find.text('Edit photo'), findsOneWidget);
|
|
final optionCard = find.descendant(
|
|
of: find.byKey(const ValueKey('edit-profile-options')),
|
|
matching: find.byType(Material),
|
|
);
|
|
expect(tester.getSize(optionCard.first).height, greaterThan(150));
|
|
for (final key in const [
|
|
'edit-profile-display-name',
|
|
'edit-profile-description',
|
|
'edit-profile-photo',
|
|
]) {
|
|
expect(
|
|
tester
|
|
.widget<AppListRow>(find.byKey(ValueKey(key)))
|
|
.verticalPadding,
|
|
Grid.xs,
|
|
);
|
|
}
|
|
expect(
|
|
find.byKey(const ValueKey('settings-edit-profile')),
|
|
findsNothing,
|
|
);
|
|
expect(find.byType(BottomSheet), findsNothing);
|
|
expect(
|
|
tester.widget<AppListCard>(find.byType(AppListCard).first).key,
|
|
const ValueKey('status-identity-options'),
|
|
);
|
|
if (Platform.environment['PROFILE_SCREENSHOTS'] case final directory?) {
|
|
final boundary = tester
|
|
.element(find.byType(SettingsPage))
|
|
.findAncestorRenderObjectOfType<RenderRepaintBoundary>()!;
|
|
await tester.runAsync(() async {
|
|
final image = await boundary.toImage(pixelRatio: 2);
|
|
final bytes = await image.toByteData(
|
|
format: ui.ImageByteFormat.png,
|
|
);
|
|
await Directory(directory).create(recursive: true);
|
|
await File(
|
|
'$directory/profile-${brightness.name}.png',
|
|
).writeAsBytes(bytes!.buffer.asUint8List());
|
|
image.dispose();
|
|
});
|
|
}
|
|
await tester.tap(find.byKey(const ValueKey('edit-profile-photo')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Profile editor destination'), findsOneWidget);
|
|
},
|
|
);
|
|
}
|
|
|
|
testWidgets('opens profile text editors directly from settings', (
|
|
tester,
|
|
) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final opened = <String>[];
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [savedPrefsProvider.overrideWithValue(prefs)],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
onSetStatus: (_) => opened.add('status'),
|
|
onEditDisplayName: (_) async => opened.add('name'),
|
|
onEditProfileDescription: (_) async => opened.add('description'),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
tester.getTopLeft(find.text('Set status')).dy,
|
|
lessThan(tester.getTopLeft(find.text('Display name')).dy),
|
|
);
|
|
await tester.tap(find.text('Set status'));
|
|
expect(opened, ['status']);
|
|
await tester.tap(find.byKey(const ValueKey('edit-profile-display-name')));
|
|
await tester.pumpAndSettle();
|
|
expect(opened, ['status', 'name']);
|
|
expect(find.text('Edit profile'), findsNothing);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('edit-profile-description')));
|
|
await tester.pumpAndSettle();
|
|
expect(opened, ['status', 'name', 'description']);
|
|
});
|
|
|
|
testWidgets('uses native navigation with Close and no Edit action on iOS', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [savedPrefsProvider.overrideWithValue(prefs)],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final nativeBar = tester.widget<UiKitView>(find.byType(UiKitView));
|
|
expect(nativeBar.viewType, 'buzz/ios_navigation_bar');
|
|
final params = nativeBar.creationParams! as Map<String, Object?>;
|
|
expect(params['title'], 'Settings');
|
|
expect(params['largeTitle'], isFalse);
|
|
expect(params['leading'], containsPair('symbol', 'xmark'));
|
|
expect(params['leading'], containsPair('label', 'Close settings'));
|
|
expect(params['actions'], isEmpty);
|
|
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets('keeps community controls out of personal settings', (
|
|
tester,
|
|
) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
currentCommunityRoleProvider.overrideWithValue(
|
|
const AsyncData<CommunityMemberRole?>(CommunityMemberRole.admin),
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Invite to community'), findsNothing);
|
|
expect(
|
|
find.text('Add people directly or share an invite link'),
|
|
findsNothing,
|
|
);
|
|
});
|
|
|
|
testWidgets('keeps personal settings independent of community role errors', (
|
|
tester,
|
|
) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
currentCommunityRoleProvider.overrideWithValue(
|
|
AsyncError<CommunityMemberRole?>(
|
|
Exception('membership query failed'),
|
|
StackTrace.empty,
|
|
),
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Invite to community'), findsNothing);
|
|
});
|
|
|
|
testWidgets('hides community invite navigation from plain members', (
|
|
tester,
|
|
) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
currentCommunityRoleProvider.overrideWithValue(
|
|
const AsyncData<CommunityMemberRole?>(CommunityMemberRole.member),
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox.shrink(),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Invite to community'), findsNothing);
|
|
});
|
|
|
|
testWidgets('uses a 24dp rhythm between profile settings groups', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(390, 1600);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
currentCommunityRoleProvider.overrideWithValue(
|
|
const AsyncData<CommunityMemberRole?>(CommunityMemberRole.admin),
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: SettingsPage(
|
|
profileHeader: const SizedBox(height: 100),
|
|
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final sections = tester.widgetList<AppListCard>(find.byType(AppListCard));
|
|
expect(sections, isNotEmpty);
|
|
expect(
|
|
sections.every((section) => section.verticalPadding == Grid.twelve),
|
|
isTrue,
|
|
);
|
|
});
|
|
}
|
|
|
|
class _SettingsLifecycleNotifier extends AppLifecycleNotifier {
|
|
@override
|
|
AppLifecycleState build() => AppLifecycleState.resumed;
|
|
}
|
|
|
|
const _pushDescriptor = BuzzPushLeaseDescriptor(
|
|
origin: 'wss://relay.example',
|
|
executorKeyId: 'key',
|
|
executorPubkey: 'pubkey',
|
|
transport: 'apns',
|
|
maxLeaseTtlSeconds: 3600,
|
|
maxContentLength: 4096,
|
|
maxPlaintextLength: 4096,
|
|
maxEndpointLength: 2048,
|
|
maxStringLength: 512,
|
|
);
|