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'**
|
/// **'Monthly'**
|
||||||
String get 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.
|
/// No description provided for @proFeature2.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -1432,12 +1426,6 @@ abstract class AppLocalizations {
|
||||||
/// **'Restore flames'**
|
/// **'Restore flames'**
|
||||||
String get proFeature4;
|
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.
|
/// No description provided for @familyFeature2.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -1462,12 +1450,6 @@ abstract class AppLocalizations {
|
||||||
/// **'10 Media file uploads per day'**
|
/// **'10 Media file uploads per day'**
|
||||||
String get freeFeature1;
|
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.
|
/// No description provided for @plusFeature2.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -756,9 +756,6 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get monthly => 'شهريًا';
|
String get monthly => 'شهريًا';
|
||||||
|
|
||||||
@override
|
|
||||||
String get proFeature1 => 'رفع غير محدود لملفات الوسائط';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature2 => 'مستخدم Plus إضافي واحد';
|
String get proFeature2 => 'مستخدم Plus إضافي واحد';
|
||||||
|
|
||||||
|
|
@ -768,9 +765,6 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get proFeature4 => 'استعادة الشعلات';
|
String get proFeature4 => 'استعادة الشعلات';
|
||||||
|
|
||||||
@override
|
|
||||||
String get familyFeature1 => 'رفع غير محدود لملفات الوسائط';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature2 => '4 مستخدمين Plus إضافيين';
|
String get familyFeature2 => '4 مستخدمين Plus إضافيين';
|
||||||
|
|
||||||
|
|
@ -783,9 +777,6 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get freeFeature1 => 'رفع 10 ملفات وسائط يوميًا';
|
String get freeFeature1 => 'رفع 10 ملفات وسائط يوميًا';
|
||||||
|
|
||||||
@override
|
|
||||||
String get plusFeature1 => 'رفع غير محدود لملفات الوسائط';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get plusFeature2 => 'ميزات إضافية (قريبًا)';
|
String get plusFeature2 => 'ميزات إضافية (قريبًا)';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -754,9 +754,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get monthly => 'Monatlich';
|
String get monthly => 'Monatlich';
|
||||||
|
|
||||||
@override
|
|
||||||
String get proFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature2 => '1 zusätzlicher Plus Benutzer';
|
String get proFeature2 => '1 zusätzlicher Plus Benutzer';
|
||||||
|
|
||||||
|
|
@ -766,9 +763,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get proFeature4 => 'Flammen wiederherstellen';
|
String get proFeature4 => 'Flammen wiederherstellen';
|
||||||
|
|
||||||
@override
|
|
||||||
String get familyFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature2 => '4 zusätzliche Plus Benutzer';
|
String get familyFeature2 => '4 zusätzliche Plus Benutzer';
|
||||||
|
|
||||||
|
|
@ -781,9 +775,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get freeFeature1 => '10 Medien-Datei-Uploads pro Tag';
|
String get freeFeature1 => '10 Medien-Datei-Uploads pro Tag';
|
||||||
|
|
||||||
@override
|
|
||||||
String get plusFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get plusFeature2 => 'Zusatzfunktionen (coming-soon)';
|
String get plusFeature2 => 'Zusatzfunktionen (coming-soon)';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -750,9 +750,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get monthly => 'Monthly';
|
String get monthly => 'Monthly';
|
||||||
|
|
||||||
@override
|
|
||||||
String get proFeature1 => 'Unlimited media file uploads';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature2 => '1 additional Plus user';
|
String get proFeature2 => '1 additional Plus user';
|
||||||
|
|
||||||
|
|
@ -762,9 +759,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get proFeature4 => 'Restore flames';
|
String get proFeature4 => 'Restore flames';
|
||||||
|
|
||||||
@override
|
|
||||||
String get familyFeature1 => 'Unlimited media file uploads';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature2 => '4 additional Plus user';
|
String get familyFeature2 => '4 additional Plus user';
|
||||||
|
|
||||||
|
|
@ -777,9 +771,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get freeFeature1 => '10 Media file uploads per day';
|
String get freeFeature1 => '10 Media file uploads per day';
|
||||||
|
|
||||||
@override
|
|
||||||
String get plusFeature1 => 'Unlimited media file uploads';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get plusFeature2 => 'Additional features (coming-soon)';
|
String get plusFeature2 => 'Additional features (coming-soon)';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -218,16 +218,13 @@
|
||||||
"yearly": "سنويًا",
|
"yearly": "سنويًا",
|
||||||
"month": "شهر",
|
"month": "شهر",
|
||||||
"monthly": "شهريًا",
|
"monthly": "شهريًا",
|
||||||
"proFeature1": "رفع غير محدود لملفات الوسائط",
|
|
||||||
"proFeature2": "مستخدم Plus إضافي واحد",
|
"proFeature2": "مستخدم Plus إضافي واحد",
|
||||||
"proFeature3": "مساحة 25 GB لتخزين الذكريات",
|
"proFeature3": "مساحة 25 GB لتخزين الذكريات",
|
||||||
"proFeature4": "استعادة الشعلات",
|
"proFeature4": "استعادة الشعلات",
|
||||||
"familyFeature1": "رفع غير محدود لملفات الوسائط",
|
|
||||||
"familyFeature2": "4 مستخدمين Plus إضافيين",
|
"familyFeature2": "4 مستخدمين Plus إضافيين",
|
||||||
"familyFeature3": "مساحة 50 GB لتخزين الذكريات",
|
"familyFeature3": "مساحة 50 GB لتخزين الذكريات",
|
||||||
"familyFeature4": "استعادة الشعلات",
|
"familyFeature4": "استعادة الشعلات",
|
||||||
"freeFeature1": "رفع 10 ملفات وسائط يوميًا",
|
"freeFeature1": "رفع 10 ملفات وسائط يوميًا",
|
||||||
"plusFeature1": "رفع غير محدود لملفات الوسائط",
|
|
||||||
"plusFeature2": "ميزات إضافية (قريبًا)",
|
"plusFeature2": "ميزات إضافية (قريبًا)",
|
||||||
"manageAdditionalUsers": "إدارة المستخدمين الإضافيين",
|
"manageAdditionalUsers": "إدارة المستخدمين الإضافيين",
|
||||||
"open": "فتح",
|
"open": "فتح",
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,6 @@
|
||||||
"errorPlanNotAllowed": "Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.",
|
"errorPlanNotAllowed": "Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.",
|
||||||
"errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.",
|
"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.",
|
"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",
|
"proFeature2": "1 zusätzlicher Plus Benutzer",
|
||||||
"proFeature3": "25GB Memories Speicher",
|
"proFeature3": "25GB Memories Speicher",
|
||||||
"proFeature4": "Flammen wiederherstellen",
|
"proFeature4": "Flammen wiederherstellen",
|
||||||
|
|
@ -263,12 +262,10 @@
|
||||||
"month": "Monat",
|
"month": "Monat",
|
||||||
"yearly": "Jährlich",
|
"yearly": "Jährlich",
|
||||||
"monthly": "Monatlich",
|
"monthly": "Monatlich",
|
||||||
"familyFeature1": "Unbegrenzte Medien-Datei-Uploads",
|
|
||||||
"familyFeature2": "4 zusätzliche Plus Benutzer",
|
"familyFeature2": "4 zusätzliche Plus Benutzer",
|
||||||
"familyFeature3": "50GB Memories Speicher",
|
"familyFeature3": "50GB Memories Speicher",
|
||||||
"familyFeature4": "Flammen wiederherstellen",
|
"familyFeature4": "Flammen wiederherstellen",
|
||||||
"freeFeature1": "10 Medien-Datei-Uploads pro Tag",
|
"freeFeature1": "10 Medien-Datei-Uploads pro Tag",
|
||||||
"plusFeature1": "Unbegrenzte Medien-Datei-Uploads",
|
|
||||||
"plusFeature2": "Zusatzfunktionen (coming-soon)",
|
"plusFeature2": "Zusatzfunktionen (coming-soon)",
|
||||||
"manageAdditionalUsers": "Zusätzliche Benutzer verwalten",
|
"manageAdditionalUsers": "Zusätzliche Benutzer verwalten",
|
||||||
"open": "Offene",
|
"open": "Offene",
|
||||||
|
|
|
||||||
|
|
@ -269,16 +269,13 @@
|
||||||
"yearly": "Yearly",
|
"yearly": "Yearly",
|
||||||
"month": "month",
|
"month": "month",
|
||||||
"monthly": "Monthly",
|
"monthly": "Monthly",
|
||||||
"proFeature1": "Unlimited media file uploads",
|
|
||||||
"proFeature2": "1 additional Plus user",
|
"proFeature2": "1 additional Plus user",
|
||||||
"proFeature3": "25GB Memories storage",
|
"proFeature3": "25GB Memories storage",
|
||||||
"proFeature4": "Restore flames",
|
"proFeature4": "Restore flames",
|
||||||
"familyFeature1": "Unlimited media file uploads",
|
|
||||||
"familyFeature2": "4 additional Plus user",
|
"familyFeature2": "4 additional Plus user",
|
||||||
"familyFeature3": "50GB Memories storage",
|
"familyFeature3": "50GB Memories storage",
|
||||||
"familyFeature4": "Restore flames",
|
"familyFeature4": "Restore flames",
|
||||||
"freeFeature1": "10 Media file uploads per day",
|
"freeFeature1": "10 Media file uploads per day",
|
||||||
"plusFeature1": "Unlimited media file uploads",
|
|
||||||
"plusFeature2": "Additional features (coming-soon)",
|
"plusFeature2": "Additional features (coming-soon)",
|
||||||
"manageAdditionalUsers": "Manage additional users",
|
"manageAdditionalUsers": "Manage additional users",
|
||||||
"open": "Open",
|
"open": "Open",
|
||||||
|
|
|
||||||
|
|
@ -184,10 +184,14 @@ class EmojiPicker extends StatefulWidget {
|
||||||
|
|
||||||
/// EmojiPickerState
|
/// EmojiPickerState
|
||||||
class EmojiPickerState extends State<EmojiPicker> {
|
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);
|
List<RecentEmoji> _recentEmoji = List.empty(growable: true);
|
||||||
late EmojiViewState _state;
|
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
|
// Prevent emojis to be reloaded with every build
|
||||||
bool _loaded = false;
|
bool _loaded = false;
|
||||||
|
|
||||||
|
|
@ -243,8 +247,10 @@ class EmojiPickerState extends State<EmojiPicker> {
|
||||||
if (oldWidget.config.height != widget.config.height) {
|
if (oldWidget.config.height != widget.config.height) {
|
||||||
_currentPickerHeight = widget.config.height;
|
_currentPickerHeight = widget.config.height;
|
||||||
}
|
}
|
||||||
if (oldWidget.config != widget.config) {
|
if (_emojiDataConfigChanged(oldWidget.config, widget.config)) {
|
||||||
// Config changed - rebuild EmojiPickerView completely
|
// 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;
|
_loaded = false;
|
||||||
_updateEmojis();
|
_updateEmojis();
|
||||||
}
|
}
|
||||||
|
|
@ -264,6 +270,15 @@ class EmojiPickerState extends State<EmojiPicker> {
|
||||||
super.didUpdateWidget(oldWidget);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.alternateView != null) {
|
if (widget.alternateView != null) {
|
||||||
|
|
@ -445,33 +460,44 @@ class EmojiPickerState extends State<EmojiPicker> {
|
||||||
|
|
||||||
// Initialize emoji data
|
// Initialize emoji data
|
||||||
Future<void> _updateEmojis() async {
|
Future<void> _updateEmojis() async {
|
||||||
_categoryEmoji.clear();
|
final loadGeneration = ++_emojiLoadGeneration;
|
||||||
|
final config = widget.config;
|
||||||
|
final categoryEmoji = <CategoryEmoji>[];
|
||||||
|
var recentEmoji = _recentEmoji;
|
||||||
|
|
||||||
if ([
|
if ([
|
||||||
RecentTabBehavior.RECENT,
|
RecentTabBehavior.RECENT,
|
||||||
RecentTabBehavior.POPULAR,
|
RecentTabBehavior.POPULAR,
|
||||||
].contains(widget.config.categoryViewConfig.recentTabBehavior)) {
|
].contains(config.categoryViewConfig.recentTabBehavior)) {
|
||||||
final futureOrRecent = _emojiPickerInternalUtils.getRecentEmojis();
|
final futureOrRecent = _emojiPickerInternalUtils.getRecentEmojis();
|
||||||
_recentEmoji = futureOrRecent is List<RecentEmoji>
|
recentEmoji = futureOrRecent is List<RecentEmoji>
|
||||||
? futureOrRecent
|
? futureOrRecent
|
||||||
: await futureOrRecent;
|
: await futureOrRecent;
|
||||||
final recentEmojiMap = _recentEmoji.map((e) => e.emoji).toList();
|
final recentEmojiMap = recentEmoji.map((e) => e.emoji).toList();
|
||||||
_categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
|
categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
|
||||||
}
|
}
|
||||||
final data =
|
final data =
|
||||||
widget.config.emojiSet?.call(widget.config.locale) ??
|
config.emojiSet?.call(config.locale) ??
|
||||||
getDefaultEmojiLocale(widget.config.locale);
|
getDefaultEmojiLocale(config.locale);
|
||||||
if (widget.config.checkPlatformCompatibility) {
|
if (config.checkPlatformCompatibility) {
|
||||||
final futureOrCategories = _emojiPickerInternalUtils.filterUnsupported(
|
final futureOrCategories = _emojiPickerInternalUtils.filterUnsupported(
|
||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
_categoryEmoji.addAll(
|
categoryEmoji.addAll(
|
||||||
futureOrCategories is List<CategoryEmoji>
|
futureOrCategories is List<CategoryEmoji>
|
||||||
? futureOrCategories
|
? futureOrCategories
|
||||||
: await futureOrCategories,
|
: await futureOrCategories,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_categoryEmoji.addAll(data);
|
categoryEmoji.addAll(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mounted || loadGeneration != _emojiLoadGeneration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_recentEmoji = recentEmoji;
|
||||||
|
_categoryEmoji = categoryEmoji;
|
||||||
_state = EmojiViewState(
|
_state = EmojiViewState(
|
||||||
_categoryEmoji,
|
_categoryEmoji,
|
||||||
_onEmojiSelected,
|
_onEmojiSelected,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class DefaultEmojiPickerView extends EmojiPickerView {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
||||||
with SingleTickerProviderStateMixin, SkinToneOverlayStateMixin {
|
with TickerProviderStateMixin, SkinToneOverlayStateMixin {
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
late PageController _legacyPageController;
|
late PageController _legacyPageController;
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
|
|
@ -39,12 +39,65 @@ class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
// Use controller's current category if available,
|
final initCategory = _createCategoryControllers();
|
||||||
// otherwise use config's initCategory
|
_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 =
|
final targetCategory =
|
||||||
widget.state.currentCategory ??
|
widget.state.currentCategory ??
|
||||||
|
preferredCategory ??
|
||||||
widget.config.categoryViewConfig.initCategory;
|
widget.config.categoryViewConfig.initCategory;
|
||||||
|
|
||||||
var initCategory = widget.state.categoryEmoji.indexWhere(
|
var initCategory = widget.state.categoryEmoji.indexWhere(
|
||||||
(element) => element.category == targetCategory,
|
(element) => element.category == targetCategory,
|
||||||
);
|
);
|
||||||
|
|
@ -62,22 +115,7 @@ class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
||||||
initialPage: initCategory,
|
initialPage: initCategory,
|
||||||
onPageRequested: _scrollToCategory,
|
onPageRequested: _scrollToCategory,
|
||||||
);
|
);
|
||||||
_scrollController.addListener(_onEmojiScroll);
|
return initCategory;
|
||||||
|
|
||||||
// Listen to programmatic category changes from controller
|
|
||||||
widget.state.categoryNavigationNotifier.addListener(
|
|
||||||
_onCategoryNavigationChanged,
|
|
||||||
);
|
|
||||||
|
|
||||||
_loadRememberedSkinTone();
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (mounted) {
|
|
||||||
_scrollToCategory(initCategory, animate: false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadRememberedSkinTone() {
|
void _loadRememberedSkinTone() {
|
||||||
|
|
@ -110,6 +148,7 @@ class _DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
||||||
_onCategoryNavigationChanged,
|
_onCategoryNavigationChanged,
|
||||||
);
|
);
|
||||||
closeSkinToneOverlay();
|
closeSkinToneOverlay();
|
||||||
|
_tabController.dispose();
|
||||||
_legacyPageController.dispose();
|
_legacyPageController.dispose();
|
||||||
_scrollController.removeListener(_onEmojiScroll);
|
_scrollController.removeListener(_onEmojiScroll);
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
|
|
|
||||||
|
|
@ -368,21 +368,19 @@ class _PlanCardState extends State<PlanCard> {
|
||||||
case 'Free':
|
case 'Free':
|
||||||
features = [context.lang.freeFeature1];
|
features = [context.lang.freeFeature1];
|
||||||
case 'Plus':
|
case 'Plus':
|
||||||
features = [context.lang.plusFeature1]; //, context.lang.plusFeature2];
|
features = [];
|
||||||
case 'Tester':
|
case 'Tester':
|
||||||
case 'Pro':
|
case 'Pro':
|
||||||
features = [
|
features = [
|
||||||
context.lang.proFeature1,
|
|
||||||
context.lang.proFeature2,
|
|
||||||
context.lang.proFeature3,
|
context.lang.proFeature3,
|
||||||
|
context.lang.proFeature2,
|
||||||
context.lang.proFeature4,
|
context.lang.proFeature4,
|
||||||
context.lang.subscriptionAllApps,
|
context.lang.subscriptionAllApps,
|
||||||
];
|
];
|
||||||
case 'Family':
|
case 'Family':
|
||||||
features = [
|
features = [
|
||||||
context.lang.familyFeature1,
|
|
||||||
context.lang.familyFeature2,
|
|
||||||
context.lang.familyFeature3,
|
context.lang.familyFeature3,
|
||||||
|
context.lang.familyFeature2,
|
||||||
context.lang.familyFeature4,
|
context.lang.familyFeature4,
|
||||||
context.lang.subscriptionAllApps,
|
context.lang.subscriptionAllApps,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,13 @@ use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, LazyLock};
|
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 MAX_BATCH_SIZE: i64 = 100;
|
||||||
const EN_ARB: &str = include_str!("../../../lib/src/localization/translations/en.arb");
|
const EN_ARB: &str = include_str!("../../../lib/src/localization/translations/en.arb");
|
||||||
const DE_ARB: &str = include_str!("../../../lib/src/localization/translations/de.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/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.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.dart';
|
||||||
import 'package:twonly/src/visual/components/emoji_picker/emoji_picker_internal_utils.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
|
// Use for golden tests, helpful in debugging
|
||||||
// await expectLater(
|
// await expectLater(
|
||||||
// find.byType(MaterialApp),
|
// find.byType(MaterialApp),
|
||||||
|
|
@ -18,7 +24,10 @@ import 'package:twonly/src/visual/components/emoji_picker/emoji_picker_internal_
|
||||||
void main() {
|
void main() {
|
||||||
group('EmojiPicker Tests', () {
|
group('EmojiPicker Tests', () {
|
||||||
// Caches are static and would otherwise leak between test cases.
|
// 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', (
|
testWidgets('Should allow user to select an emoji', (
|
||||||
tester,
|
tester,
|
||||||
|
|
@ -135,6 +144,76 @@ void main() {
|
||||||
expect(scrollable.position.pixels, greaterThan(0));
|
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', (
|
testWidgets('Drag handle grows picker from its configured height', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue