mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
fix plans and exception
This commit is contained in:
parent
76a9455c7c
commit
7d17ffba1f
12 changed files with 188 additions and 93 deletions
|
|
@ -1408,12 +1408,6 @@ abstract class AppLocalizations {
|
|||
/// **'Monthly'**
|
||||
String get monthly;
|
||||
|
||||
/// No description provided for @proFeature1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unlimited media file uploads'**
|
||||
String get proFeature1;
|
||||
|
||||
/// No description provided for @proFeature2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1432,12 +1426,6 @@ abstract class AppLocalizations {
|
|||
/// **'Restore flames'**
|
||||
String get proFeature4;
|
||||
|
||||
/// No description provided for @familyFeature1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unlimited media file uploads'**
|
||||
String get familyFeature1;
|
||||
|
||||
/// No description provided for @familyFeature2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1462,12 +1450,6 @@ abstract class AppLocalizations {
|
|||
/// **'10 Media file uploads per day'**
|
||||
String get freeFeature1;
|
||||
|
||||
/// No description provided for @plusFeature1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unlimited media file uploads'**
|
||||
String get plusFeature1;
|
||||
|
||||
/// No description provided for @plusFeature2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -756,9 +756,6 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get monthly => 'شهريًا';
|
||||
|
||||
@override
|
||||
String get proFeature1 => 'رفع غير محدود لملفات الوسائط';
|
||||
|
||||
@override
|
||||
String get proFeature2 => 'مستخدم Plus إضافي واحد';
|
||||
|
||||
|
|
@ -768,9 +765,6 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get proFeature4 => 'استعادة الشعلات';
|
||||
|
||||
@override
|
||||
String get familyFeature1 => 'رفع غير محدود لملفات الوسائط';
|
||||
|
||||
@override
|
||||
String get familyFeature2 => '4 مستخدمين Plus إضافيين';
|
||||
|
||||
|
|
@ -783,9 +777,6 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get freeFeature1 => 'رفع 10 ملفات وسائط يوميًا';
|
||||
|
||||
@override
|
||||
String get plusFeature1 => 'رفع غير محدود لملفات الوسائط';
|
||||
|
||||
@override
|
||||
String get plusFeature2 => 'ميزات إضافية (قريبًا)';
|
||||
|
||||
|
|
|
|||
|
|
@ -754,9 +754,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get monthly => 'Monatlich';
|
||||
|
||||
@override
|
||||
String get proFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
||||
|
||||
@override
|
||||
String get proFeature2 => '1 zusätzlicher Plus Benutzer';
|
||||
|
||||
|
|
@ -766,9 +763,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get proFeature4 => 'Flammen wiederherstellen';
|
||||
|
||||
@override
|
||||
String get familyFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
||||
|
||||
@override
|
||||
String get familyFeature2 => '4 zusätzliche Plus Benutzer';
|
||||
|
||||
|
|
@ -781,9 +775,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get freeFeature1 => '10 Medien-Datei-Uploads pro Tag';
|
||||
|
||||
@override
|
||||
String get plusFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
||||
|
||||
@override
|
||||
String get plusFeature2 => 'Zusatzfunktionen (coming-soon)';
|
||||
|
||||
|
|
|
|||
|
|
@ -750,9 +750,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get monthly => 'Monthly';
|
||||
|
||||
@override
|
||||
String get proFeature1 => 'Unlimited media file uploads';
|
||||
|
||||
@override
|
||||
String get proFeature2 => '1 additional Plus user';
|
||||
|
||||
|
|
@ -762,9 +759,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get proFeature4 => 'Restore flames';
|
||||
|
||||
@override
|
||||
String get familyFeature1 => 'Unlimited media file uploads';
|
||||
|
||||
@override
|
||||
String get familyFeature2 => '4 additional Plus user';
|
||||
|
||||
|
|
@ -777,9 +771,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get freeFeature1 => '10 Media file uploads per day';
|
||||
|
||||
@override
|
||||
String get plusFeature1 => 'Unlimited media file uploads';
|
||||
|
||||
@override
|
||||
String get plusFeature2 => 'Additional features (coming-soon)';
|
||||
|
||||
|
|
|
|||
|
|
@ -218,16 +218,13 @@
|
|||
"yearly": "سنويًا",
|
||||
"month": "شهر",
|
||||
"monthly": "شهريًا",
|
||||
"proFeature1": "رفع غير محدود لملفات الوسائط",
|
||||
"proFeature2": "مستخدم Plus إضافي واحد",
|
||||
"proFeature3": "مساحة 25 GB لتخزين الذكريات",
|
||||
"proFeature4": "استعادة الشعلات",
|
||||
"familyFeature1": "رفع غير محدود لملفات الوسائط",
|
||||
"familyFeature2": "4 مستخدمين Plus إضافيين",
|
||||
"familyFeature3": "مساحة 50 GB لتخزين الذكريات",
|
||||
"familyFeature4": "استعادة الشعلات",
|
||||
"freeFeature1": "رفع 10 ملفات وسائط يوميًا",
|
||||
"plusFeature1": "رفع غير محدود لملفات الوسائط",
|
||||
"plusFeature2": "ميزات إضافية (قريبًا)",
|
||||
"manageAdditionalUsers": "إدارة المستخدمين الإضافيين",
|
||||
"open": "فتح",
|
||||
|
|
|
|||
|
|
@ -255,7 +255,6 @@
|
|||
"errorPlanNotAllowed": "Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.",
|
||||
"errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.",
|
||||
"errorPlanUpgradeNotYearly": "Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.",
|
||||
"proFeature1": "Unbegrenzte Medien-Datei-Uploads",
|
||||
"proFeature2": "1 zusätzlicher Plus Benutzer",
|
||||
"proFeature3": "25GB Memories Speicher",
|
||||
"proFeature4": "Flammen wiederherstellen",
|
||||
|
|
@ -263,12 +262,10 @@
|
|||
"month": "Monat",
|
||||
"yearly": "Jährlich",
|
||||
"monthly": "Monatlich",
|
||||
"familyFeature1": "Unbegrenzte Medien-Datei-Uploads",
|
||||
"familyFeature2": "4 zusätzliche Plus Benutzer",
|
||||
"familyFeature3": "50GB Memories Speicher",
|
||||
"familyFeature4": "Flammen wiederherstellen",
|
||||
"freeFeature1": "10 Medien-Datei-Uploads pro Tag",
|
||||
"plusFeature1": "Unbegrenzte Medien-Datei-Uploads",
|
||||
"plusFeature2": "Zusatzfunktionen (coming-soon)",
|
||||
"manageAdditionalUsers": "Zusätzliche Benutzer verwalten",
|
||||
"open": "Offene",
|
||||
|
|
|
|||
|
|
@ -269,16 +269,13 @@
|
|||
"yearly": "Yearly",
|
||||
"month": "month",
|
||||
"monthly": "Monthly",
|
||||
"proFeature1": "Unlimited media file uploads",
|
||||
"proFeature2": "1 additional Plus user",
|
||||
"proFeature3": "25GB Memories storage",
|
||||
"proFeature4": "Restore flames",
|
||||
"familyFeature1": "Unlimited media file uploads",
|
||||
"familyFeature2": "4 additional Plus user",
|
||||
"familyFeature3": "50GB Memories storage",
|
||||
"familyFeature4": "Restore flames",
|
||||
"freeFeature1": "10 Media file uploads per day",
|
||||
"plusFeature1": "Unlimited media file uploads",
|
||||
"plusFeature2": "Additional features (coming-soon)",
|
||||
"manageAdditionalUsers": "Manage additional users",
|
||||
"open": "Open",
|
||||
|
|
|
|||
|
|
@ -184,10 +184,14 @@ class EmojiPicker extends StatefulWidget {
|
|||
|
||||
/// EmojiPickerState
|
||||
class EmojiPickerState extends State<EmojiPicker> {
|
||||
final List<CategoryEmoji> _categoryEmoji = List.empty(growable: true);
|
||||
List<CategoryEmoji> _categoryEmoji = List.empty(growable: true);
|
||||
List<RecentEmoji> _recentEmoji = List.empty(growable: true);
|
||||
late EmojiViewState _state;
|
||||
|
||||
// Identifies the newest asynchronous emoji load so an older load cannot
|
||||
// replace data produced for a more recent config.
|
||||
int _emojiLoadGeneration = 0;
|
||||
|
||||
// Prevent emojis to be reloaded with every build
|
||||
bool _loaded = false;
|
||||
|
||||
|
|
@ -243,8 +247,10 @@ class EmojiPickerState extends State<EmojiPicker> {
|
|||
if (oldWidget.config.height != widget.config.height) {
|
||||
_currentPickerHeight = widget.config.height;
|
||||
}
|
||||
if (oldWidget.config != widget.config) {
|
||||
// Config changed - rebuild EmojiPickerView completely
|
||||
if (_emojiDataConfigChanged(oldWidget.config, widget.config)) {
|
||||
// Only reload data when a setting that affects the category contents
|
||||
// changes. Visual config and callback changes already flow through the
|
||||
// normal widget rebuild and must not start overlapping async loads.
|
||||
_loaded = false;
|
||||
_updateEmojis();
|
||||
}
|
||||
|
|
@ -264,6 +270,15 @@ class EmojiPickerState extends State<EmojiPicker> {
|
|||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
bool _emojiDataConfigChanged(Config oldConfig, Config newConfig) {
|
||||
return oldConfig.locale != newConfig.locale ||
|
||||
oldConfig.emojiSet != newConfig.emojiSet ||
|
||||
oldConfig.checkPlatformCompatibility !=
|
||||
newConfig.checkPlatformCompatibility ||
|
||||
oldConfig.categoryViewConfig.recentTabBehavior !=
|
||||
newConfig.categoryViewConfig.recentTabBehavior;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.alternateView != null) {
|
||||
|
|
@ -445,33 +460,44 @@ class EmojiPickerState extends State<EmojiPicker> {
|
|||
|
||||
// Initialize emoji data
|
||||
Future<void> _updateEmojis() async {
|
||||
_categoryEmoji.clear();
|
||||
final loadGeneration = ++_emojiLoadGeneration;
|
||||
final config = widget.config;
|
||||
final categoryEmoji = <CategoryEmoji>[];
|
||||
var recentEmoji = _recentEmoji;
|
||||
|
||||
if ([
|
||||
RecentTabBehavior.RECENT,
|
||||
RecentTabBehavior.POPULAR,
|
||||
].contains(widget.config.categoryViewConfig.recentTabBehavior)) {
|
||||
].contains(config.categoryViewConfig.recentTabBehavior)) {
|
||||
final futureOrRecent = _emojiPickerInternalUtils.getRecentEmojis();
|
||||
_recentEmoji = futureOrRecent is List<RecentEmoji>
|
||||
recentEmoji = futureOrRecent is List<RecentEmoji>
|
||||
? futureOrRecent
|
||||
: await futureOrRecent;
|
||||
final recentEmojiMap = _recentEmoji.map((e) => e.emoji).toList();
|
||||
_categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
|
||||
final recentEmojiMap = recentEmoji.map((e) => e.emoji).toList();
|
||||
categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
|
||||
}
|
||||
final data =
|
||||
widget.config.emojiSet?.call(widget.config.locale) ??
|
||||
getDefaultEmojiLocale(widget.config.locale);
|
||||
if (widget.config.checkPlatformCompatibility) {
|
||||
config.emojiSet?.call(config.locale) ??
|
||||
getDefaultEmojiLocale(config.locale);
|
||||
if (config.checkPlatformCompatibility) {
|
||||
final futureOrCategories = _emojiPickerInternalUtils.filterUnsupported(
|
||||
data,
|
||||
);
|
||||
_categoryEmoji.addAll(
|
||||
categoryEmoji.addAll(
|
||||
futureOrCategories is List<CategoryEmoji>
|
||||
? futureOrCategories
|
||||
: await futureOrCategories,
|
||||
);
|
||||
} else {
|
||||
_categoryEmoji.addAll(data);
|
||||
categoryEmoji.addAll(data);
|
||||
}
|
||||
|
||||
if (!mounted || loadGeneration != _emojiLoadGeneration) {
|
||||
return;
|
||||
}
|
||||
|
||||
_recentEmoji = recentEmoji;
|
||||
_categoryEmoji = categoryEmoji;
|
||||
_state = EmojiViewState(
|
||||
_categoryEmoji,
|
||||
_onEmojiSelected,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class DefaultEmojiPickerView extends EmojiPickerView {
|
|||
}
|
||||
|
||||
class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
||||
with SingleTickerProviderStateMixin, SkinToneOverlayStateMixin {
|
||||
with TickerProviderStateMixin, SkinToneOverlayStateMixin {
|
||||
late TabController _tabController;
|
||||
late PageController _legacyPageController;
|
||||
final _scrollController = ScrollController();
|
||||
|
|
@ -39,12 +39,65 @@ class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
// Use controller's current category if available,
|
||||
// otherwise use config's initCategory
|
||||
final initCategory = _createCategoryControllers();
|
||||
_scrollController.addListener(_onEmojiScroll);
|
||||
|
||||
// Listen to programmatic category changes from controller
|
||||
widget.state.categoryNavigationNotifier.addListener(
|
||||
_onCategoryNavigationChanged,
|
||||
);
|
||||
|
||||
_loadRememberedSkinTone();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_scrollToCategory(initCategory, animate: false);
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant DefaultEmojiPickerView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (identical(oldWidget.state, widget.state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final previousCategory =
|
||||
_activeCategoryIndex < oldWidget.state.categoryEmoji.length
|
||||
? oldWidget.state.categoryEmoji[_activeCategoryIndex].category
|
||||
: null;
|
||||
oldWidget.state.categoryNavigationNotifier.removeListener(
|
||||
_onCategoryNavigationChanged,
|
||||
);
|
||||
closeSkinToneOverlay();
|
||||
_tabController.dispose();
|
||||
_legacyPageController.dispose();
|
||||
_categoryOffsets = const [];
|
||||
_isNavigatingToCategory = false;
|
||||
|
||||
final initCategory = _createCategoryControllers(
|
||||
preferredCategory: previousCategory,
|
||||
);
|
||||
widget.state.categoryNavigationNotifier.addListener(
|
||||
_onCategoryNavigationChanged,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_scrollToCategory(initCategory, animate: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int _createCategoryControllers({Category? preferredCategory}) {
|
||||
// Prefer an externally controlled category, then preserve the current
|
||||
// category across data reloads, and finally fall back to the config.
|
||||
final targetCategory =
|
||||
widget.state.currentCategory ??
|
||||
preferredCategory ??
|
||||
widget.config.categoryViewConfig.initCategory;
|
||||
|
||||
var initCategory = widget.state.categoryEmoji.indexWhere(
|
||||
(element) => element.category == targetCategory,
|
||||
);
|
||||
|
|
@ -62,22 +115,7 @@ class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||
initialPage: initCategory,
|
||||
onPageRequested: _scrollToCategory,
|
||||
);
|
||||
_scrollController.addListener(_onEmojiScroll);
|
||||
|
||||
// Listen to programmatic category changes from controller
|
||||
widget.state.categoryNavigationNotifier.addListener(
|
||||
_onCategoryNavigationChanged,
|
||||
);
|
||||
|
||||
_loadRememberedSkinTone();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_scrollToCategory(initCategory, animate: false);
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
return initCategory;
|
||||
}
|
||||
|
||||
void _loadRememberedSkinTone() {
|
||||
|
|
@ -110,6 +148,7 @@ class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||
_onCategoryNavigationChanged,
|
||||
);
|
||||
closeSkinToneOverlay();
|
||||
_tabController.dispose();
|
||||
_legacyPageController.dispose();
|
||||
_scrollController.removeListener(_onEmojiScroll);
|
||||
_scrollController.dispose();
|
||||
|
|
|
|||
|
|
@ -368,21 +368,19 @@ class _PlanCardState extends State<PlanCard> {
|
|||
case 'Free':
|
||||
features = [context.lang.freeFeature1];
|
||||
case 'Plus':
|
||||
features = [context.lang.plusFeature1]; //, context.lang.plusFeature2];
|
||||
features = [];
|
||||
case 'Tester':
|
||||
case 'Pro':
|
||||
features = [
|
||||
context.lang.proFeature1,
|
||||
context.lang.proFeature2,
|
||||
context.lang.proFeature3,
|
||||
context.lang.proFeature2,
|
||||
context.lang.proFeature4,
|
||||
context.lang.subscriptionAllApps,
|
||||
];
|
||||
case 'Family':
|
||||
features = [
|
||||
context.lang.familyFeature1,
|
||||
context.lang.familyFeature2,
|
||||
context.lang.familyFeature3,
|
||||
context.lang.familyFeature2,
|
||||
context.lang.familyFeature4,
|
||||
context.lang.subscriptionAllApps,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ use std::collections::HashMap;
|
|||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
// Temporary companion guard for the server's iOS 0.6.x wake-up workaround.
|
||||
// The server can bound APNs attempts, but only this device-side query can bound
|
||||
// events that were already downloaded into the local notification outbox.
|
||||
// Keep Android's existing batch size unchanged.
|
||||
#[cfg(target_os = "ios")]
|
||||
const MAX_BATCH_SIZE: i64 = 40;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
const MAX_BATCH_SIZE: i64 = 100;
|
||||
const EN_ARB: &str = include_str!("../../../lib/src/localization/translations/en.arb");
|
||||
const DE_ARB: &str = include_str!("../../../lib/src/localization/translations/de.arb");
|
||||
|
|
|
|||
|
|
@ -6,9 +6,15 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:twonly/src/visual/components/emoji_picker/emoji_picker.dart';
|
||||
import 'package:twonly/src/visual/components/emoji_picker/emoji_picker_internal_utils.dart';
|
||||
|
||||
List<CategoryEmoji> _duplicatedEmojiSet(Locale _) => [
|
||||
...defaultEmojiSet,
|
||||
...defaultEmojiSet,
|
||||
];
|
||||
|
||||
// Use for golden tests, helpful in debugging
|
||||
// await expectLater(
|
||||
// find.byType(MaterialApp),
|
||||
|
|
@ -18,7 +24,10 @@ import 'package:twonly/src/visual/components/emoji_picker/emoji_picker_internal_
|
|||
void main() {
|
||||
group('EmojiPicker Tests', () {
|
||||
// Caches are static and would otherwise leak between test cases.
|
||||
setUp(EmojiPickerInternalUtils.resetCaches);
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
EmojiPickerInternalUtils.resetCaches();
|
||||
});
|
||||
|
||||
testWidgets('Should allow user to select an emoji', (
|
||||
tester,
|
||||
|
|
@ -135,6 +144,76 @@ void main() {
|
|||
expect(scrollable.position.pixels, greaterThan(0));
|
||||
});
|
||||
|
||||
testWidgets('Recreates category navigation when the emoji set changes', (
|
||||
tester,
|
||||
) async {
|
||||
var config = const Config(checkPlatformCompatibility: false);
|
||||
late StateSetter setHarnessState;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
setHarnessState = setState;
|
||||
return EmojiPicker(config: config);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
var tabBar = tester.widget<TabBar>(find.byType(TabBar));
|
||||
expect(tabBar.controller!.length, 9);
|
||||
expect(tabBar.tabs.length, 9);
|
||||
|
||||
setHarnessState(() {
|
||||
config = const Config(
|
||||
checkPlatformCompatibility: false,
|
||||
emojiSet: _duplicatedEmojiSet,
|
||||
);
|
||||
});
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tabBar = tester.widget<TabBar>(find.byType(TabBar));
|
||||
expect(tabBar.controller!.length, 17);
|
||||
expect(tabBar.tabs.length, 17);
|
||||
});
|
||||
|
||||
testWidgets('Keeps only the newest emoji load when configs overlap', (
|
||||
tester,
|
||||
) async {
|
||||
var config = const Config(checkPlatformCompatibility: false);
|
||||
late StateSetter setHarnessState;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
setHarnessState = setState;
|
||||
return EmojiPicker(config: config);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Change the config while the first recent-emoji read is still pending.
|
||||
setHarnessState(() {
|
||||
config = const Config(
|
||||
checkPlatformCompatibility: false,
|
||||
emojiSet: _duplicatedEmojiSet,
|
||||
);
|
||||
});
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tabBar = tester.widget<TabBar>(find.byType(TabBar));
|
||||
expect(tabBar.controller!.length, 17);
|
||||
expect(tabBar.tabs.length, 17);
|
||||
});
|
||||
|
||||
testWidgets('Drag handle grows picker from its configured height', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue