Compare commits
	
		
			8 Commits
		
	
	
		
			2.1.1+38
			...
			4d96a15c31
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4d96a15c31 | |||
| 06dd3e092a | |||
| 82fe9e287a | |||
| dc1c285de1 | |||
| 5a3313e94f | |||
| 61032c84f1 | |||
| 36a5b8fb39 | |||
| 3eda464e03 | 
| @@ -378,9 +378,26 @@ | ||||
|   "dailyCheckNegativeHint5Description": "Lost connection at a crucial moment", | ||||
|   "dailyCheckNegativeHint6": "Going out", | ||||
|   "dailyCheckNegativeHint6Description": "Forgot your umbrella and got caught in the rain", | ||||
|   "happyBirthday": "Happy birthday, {}!", | ||||
|   "celebrateBirthday": "Happy birthday, {}!", | ||||
|   "celebrateMerryXmas": "Merry christmas, {}!", | ||||
|   "celebrateNewYear": "Happy new year, {}!", | ||||
|   "celebrateValentineDay": "Today is valentine's day, {}!", | ||||
|   "celebrateLaborDay": "Today is labor day, {}.", | ||||
|   "celebrateMotherDay": "Today is mother's day, {}.", | ||||
|   "celebrateChildrenDay": "Today is children's day, {}!", | ||||
|   "celebrateFatherDay": "Today is father's day, {}.", | ||||
|   "celebrateHalloween": "Happy halloween, {}!", | ||||
|   "celebrateThanksgiving": "Today is thanksgiving day, {}!", | ||||
|   "pendingBirthday": "Birthday in {}", | ||||
|   "pendingMerryXmas": "Christmas in {}", | ||||
|   "pendingNewYear": "New year in {}", | ||||
|   "pendingValentineDay": "Valentine's day in {}", | ||||
|   "pendingLaborDay": "Labor day in {}", | ||||
|   "pendingMotherDay": "Mother's day in {}", | ||||
|   "pendingChildrenDay": "Children's day in {}", | ||||
|   "pendingFatherDay": "Father's day in {}", | ||||
|   "pendingHalloween": "Halloween in {}", | ||||
|   "pendingThanksgiving": "Thanksgiving day in {}", | ||||
|   "friendNew": "Add Friend", | ||||
|   "friendRequests": "Friend Requests", | ||||
|   "friendRequestsDescription": { | ||||
| @@ -488,5 +505,6 @@ | ||||
|   "postCategoryNews": "News", | ||||
|   "postCategoryKnowledge": "Knowledge", | ||||
|   "postCategoryLiterature": "Literature", | ||||
|   "postCategoryFunny": "Funny", | ||||
|   "postCategoryUncategorized": "Uncategorized" | ||||
| } | ||||
|   | ||||
| @@ -376,9 +376,26 @@ | ||||
|   "dailyCheckNegativeHint5Description": "关键时刻断网", | ||||
|   "dailyCheckNegativeHint6": "出门", | ||||
|   "dailyCheckNegativeHint6Description": "忘带伞遇上大雨", | ||||
|   "happyBirthday": "生日快乐,{}!", | ||||
|   "celebrateBirthday": "生日快乐,{}!", | ||||
|   "celebrateMerryXmas": "圣诞快乐,{}!", | ||||
|   "celebrateNewYear": "新年快乐,{}!", | ||||
|   "celebrateValentineDay": "今天是情人节,{}!", | ||||
|   "celebrateLaborDay": "今天是劳动节,{}。", | ||||
|   "celebrateMotherDay": "今天是母亲节,{}。", | ||||
|   "celebrateChildrenDay": "今天是儿童节,{}!", | ||||
|   "celebrateFatherDay": "今天是父亲节,{}。", | ||||
|   "celebrateHalloween": "快乐在圣诞节,{}!", | ||||
|   "celebrateThanksgiving": "今天是感恩节,{}!", | ||||
|   "pendingBirthday": "{} 过生日", | ||||
|   "pendingMerryXmas": "{} 过圣诞节", | ||||
|   "pendingNewYear": "{} 跨年", | ||||
|   "pendingValentineDay": "{} 过情人节", | ||||
|   "pendingLaborDay": "{} 过劳动节", | ||||
|   "pendingMotherDay": "{} 过母亲节", | ||||
|   "pendingChildrenDay": "{} 过儿童节", | ||||
|   "pendingFatherDay": "{} 过父亲节", | ||||
|   "pendingHalloween": "{} 过圣诞节", | ||||
|   "pendingThanksgiving": "{} 过感恩节", | ||||
|   "friendNew": "添加好友", | ||||
|   "friendRequests": "好友请求", | ||||
|   "friendRequestsDescription": { | ||||
| @@ -486,5 +503,6 @@ | ||||
|   "postCategoryNews": "新闻", | ||||
|   "postCategoryKnowledge": "知识", | ||||
|   "postCategoryLiterature": "文学", | ||||
|   "postCategoryFunny": "搞笑", | ||||
|   "postCategoryUncategorized": "未分类" | ||||
| } | ||||
|   | ||||
| @@ -376,9 +376,26 @@ | ||||
|   "dailyCheckNegativeHint5Description": "關鍵時刻斷網", | ||||
|   "dailyCheckNegativeHint6": "出門", | ||||
|   "dailyCheckNegativeHint6Description": "忘帶傘遇上大雨", | ||||
|   "happyBirthday": "生日快樂,{}!", | ||||
|   "celebrateBirthday": "生日快樂,{}!", | ||||
|   "celebrateMerryXmas": "聖誕快樂,{}!", | ||||
|   "celebrateNewYear": "新年快樂,{}!", | ||||
|   "celebrateValentineDay": "今天是情人節,{}!", | ||||
|   "celebrateLaborDay": "今天是勞動節,{}。", | ||||
|   "celebrateMotherDay": "今天是母親節,{}。", | ||||
|   "celebrateChildrenDay": "今天是兒童節,{}!", | ||||
|   "celebrateFatherDay": "今天是父親節,{}。", | ||||
|   "celebrateHalloween": "快樂在聖誕節,{}!", | ||||
|   "celebrateThanksgiving": "今天是感恩節,{}!", | ||||
|   "pendingBirthday": "{} 過生日", | ||||
|   "pendingMerryXmas": "{} 過聖誕節", | ||||
|   "pendingNewYear": "{} 跨年", | ||||
|   "pendingValentineDay": "{} 過情人節", | ||||
|   "pendingLaborDay": "{} 過勞動節", | ||||
|   "pendingMotherDay": "{} 過母親節", | ||||
|   "pendingChildrenDay": "{} 過兒童節", | ||||
|   "pendingFatherDay": "{} 過父親節", | ||||
|   "pendingHalloween": "{} 過聖誕節", | ||||
|   "pendingThanksgiving": "{} 過感恩節", | ||||
|   "friendNew": "添加好友", | ||||
|   "friendRequests": "好友請求", | ||||
|   "friendRequestsDescription": { | ||||
| @@ -486,5 +503,6 @@ | ||||
|   "postCategoryNews": "新聞", | ||||
|   "postCategoryKnowledge": "知識", | ||||
|   "postCategoryLiterature": "文學", | ||||
|   "postCategoryFunny": "搞笑", | ||||
|   "postCategoryUncategorized": "未分類" | ||||
| } | ||||
|   | ||||
| @@ -376,9 +376,26 @@ | ||||
|   "dailyCheckNegativeHint5Description": "關鍵時刻斷網", | ||||
|   "dailyCheckNegativeHint6": "出門", | ||||
|   "dailyCheckNegativeHint6Description": "忘帶傘遇上大雨", | ||||
|   "happyBirthday": "生日快樂,{}!", | ||||
|   "celebrateBirthday": "生日快樂,{}!", | ||||
|   "celebrateMerryXmas": "聖誕快樂,{}!", | ||||
|   "celebrateNewYear": "新年快樂,{}!", | ||||
|   "celebrateValentineDay": "今天是情人節,{}!", | ||||
|   "celebrateLaborDay": "今天是勞動節,{}。", | ||||
|   "celebrateMotherDay": "今天是母親節,{}。", | ||||
|   "celebrateChildrenDay": "今天是兒童節,{}!", | ||||
|   "celebrateFatherDay": "今天是父親節,{}。", | ||||
|   "celebrateHalloween": "快樂在聖誕節,{}!", | ||||
|   "celebrateThanksgiving": "今天是感恩節,{}!", | ||||
|   "pendingBirthday": "{} 過生日", | ||||
|   "pendingMerryXmas": "{} 過聖誕節", | ||||
|   "pendingNewYear": "{} 跨年", | ||||
|   "pendingValentineDay": "{} 過情人節", | ||||
|   "pendingLaborDay": "{} 過勞動節", | ||||
|   "pendingMotherDay": "{} 過母親節", | ||||
|   "pendingChildrenDay": "{} 過兒童節", | ||||
|   "pendingFatherDay": "{} 過父親節", | ||||
|   "pendingHalloween": "{} 過聖誕節", | ||||
|   "pendingThanksgiving": "{} 過感恩節", | ||||
|   "friendNew": "新增好友", | ||||
|   "friendRequests": "好友請求", | ||||
|   "friendRequestsDescription": { | ||||
| @@ -486,5 +503,6 @@ | ||||
|   "postCategoryNews": "新聞", | ||||
|   "postCategoryKnowledge": "知識", | ||||
|   "postCategoryLiterature": "文學", | ||||
|   "postCategoryFunny": "搞笑", | ||||
|   "postCategoryUncategorized": "未分類" | ||||
| } | ||||
|   | ||||
| @@ -173,7 +173,7 @@ PODS: | ||||
|   - in_app_review (2.0.0): | ||||
|     - Flutter | ||||
|   - Kingfisher (8.1.3) | ||||
|   - livekit_client (2.3.2): | ||||
|   - livekit_client (2.3.3): | ||||
|     - Flutter | ||||
|     - flutter_webrtc | ||||
|     - WebRTC-SDK (= 125.6422.06) | ||||
| @@ -386,7 +386,7 @@ SPEC CHECKSUMS: | ||||
|   image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 | ||||
|   in_app_review: a31b5257259646ea78e0e35fc914979b0031d011 | ||||
|   Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef | ||||
|   livekit_client: 6108dad8b77db3142bafd4c630f471d0a54335cd | ||||
|   livekit_client: 02cf2cc4357a655af12ccee70ff5596ae4e6feef | ||||
|   media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 | ||||
|   media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a | ||||
|   media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e | ||||
|   | ||||
| @@ -80,7 +80,11 @@ class NotificationService: UNNotificationServiceExtension { | ||||
|          | ||||
|         let metadataCopy = metadata as? [String: String] ?? [:] | ||||
|         let avatarUrl = getAttachmentUrl(for: avatarIdentifier) | ||||
|         KingfisherManager.shared.retrieveImage(with: URL(string: avatarUrl)!, completionHandler: { result in | ||||
|          | ||||
|         let targetSize = 640 | ||||
|         let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit) | ||||
|          | ||||
|         KingfisherManager.shared.retrieveImage(with: URL(string: avatarUrl)!, options: [.processor(scaleProcessor)], completionHandler: { result in | ||||
|             var image: Data? | ||||
|             switch result { | ||||
|             case .success(let value): | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import 'package:surface/providers/post.dart'; | ||||
| import 'package:surface/providers/relationship.dart'; | ||||
| import 'package:surface/providers/sn_attachment.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/special_day.dart'; | ||||
| import 'package:surface/providers/theme.dart'; | ||||
| import 'package:surface/providers/user_directory.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| @@ -148,6 +149,9 @@ class SolianApp extends StatelessWidget { | ||||
|             ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)), | ||||
|             ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)), | ||||
|             ChangeNotifierProvider(create: (ctx) => ChatCallProvider(ctx)), | ||||
|  | ||||
|             // Additional helper layer | ||||
|             Provider(create: (ctx) => SpecialDayProvider(ctx)), | ||||
|           ], | ||||
|           child: _AppDelegate(), | ||||
|         ), | ||||
| @@ -265,6 +269,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> { | ||||
|       // The Network initialization will also save initialize the Config, so it not need to be initialized again | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       await sn.initializeUserAgent(); | ||||
|       await sn.setConfigWithNative(); | ||||
|       if (!mounted) return; | ||||
|       final ua = context.read<UserProvider>(); | ||||
|       await ua.initialize(); | ||||
|   | ||||
| @@ -68,9 +68,8 @@ class SnNetworkProvider { | ||||
|     _config.initialize().then((_) { | ||||
|       _prefs = _config.prefs; | ||||
|       client.options.baseUrl = _config.serverUrl; | ||||
|       if (!context.mounted) return; | ||||
|       _home.saveWidgetData("nex_server_url", client.options.baseUrl); | ||||
|     }); | ||||
|  | ||||
|   } | ||||
|  | ||||
|   static Future<Dio> createOffContextClient() async { | ||||
| @@ -109,6 +108,10 @@ class SnNetworkProvider { | ||||
|     return client; | ||||
|   } | ||||
|  | ||||
|   Future<void> setConfigWithNative() async { | ||||
|     _home.saveWidgetData("nex_server_url", client.options.baseUrl); | ||||
|   } | ||||
|  | ||||
|   static Future<String> _getUserAgent() async { | ||||
|     final String platformInfo; | ||||
|     if (kIsWeb) { | ||||
|   | ||||
							
								
								
									
										136
									
								
								lib/providers/special_day.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								lib/providers/special_day.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
|  | ||||
| // Stored as key: month, day | ||||
| const Map<String, (int, int)> kSpecialDays = { | ||||
|   // Birthday is dynamically generated according to the user's profile | ||||
|   'NewYear': (1, 1), | ||||
|   'ValentineDay': (2, 14), | ||||
|   'LaborDay': (5, 1), | ||||
|   'MotherDay': (5, 11), | ||||
|   'ChildrenDay': (6, 1), | ||||
|   'FatherDay': (8, 8), | ||||
|   'Halloween': (10, 31), | ||||
|   'Thanksgiving': (11, 28), | ||||
|   'MerryXmas': (12, 25), | ||||
| }; | ||||
|  | ||||
| const Map<String, String> kSpecialDaysSymbol = { | ||||
|   'Birthday': '🎂', | ||||
|   'NewYear': '🎉', | ||||
|   'MerryXmas': '🎄', | ||||
|   'ValentineDay': '💑', | ||||
|   'LaborDay': '🏋️', | ||||
|   'MotherDay': '👩', | ||||
|   'ChildrenDay': '👶', | ||||
|   'FatherDay': '👨', | ||||
|   'Halloween': '🎃', | ||||
|   'Thanksgiving': '🎅', | ||||
| }; | ||||
|  | ||||
| class SpecialDayProvider { | ||||
|   late final UserProvider _user; | ||||
|  | ||||
|   SpecialDayProvider(BuildContext context) { | ||||
|     _user = context.read<UserProvider>(); | ||||
|   } | ||||
|  | ||||
|   List<String> getSpecialDays() { | ||||
|     final now = DateTime.now().toLocal(); | ||||
|     final birthday = _user.user?.profile?.birthday?.toLocal(); | ||||
|     final isBirthday = birthday != null && birthday.day == now.day && birthday.month == now.month; | ||||
|  | ||||
|     return [ | ||||
|       if (isBirthday) 'Birthday', | ||||
|       ...kSpecialDays.keys.where( | ||||
|         (key) => kSpecialDays[key]!.$1 == now.month && kSpecialDays[key]!.$2 == now.day, | ||||
|       ), | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
|   (String, DateTime)? getLastSpecialDay() { | ||||
|     final now = DateTime.now().toLocal(); | ||||
|     final birthday = _user.user?.profile?.birthday?.toLocal(); | ||||
|  | ||||
|     final Map<String, (int, int)> specialDays = { | ||||
|       if (birthday != null) 'Birthday': (birthday.month, birthday.day), | ||||
|       ...kSpecialDays, | ||||
|     }; | ||||
|  | ||||
|     DateTime? lastDate; | ||||
|     String? lastEvent; | ||||
|  | ||||
|     for (final entry in specialDays.entries) { | ||||
|       final eventName = entry.key; | ||||
|       final (month, day) = entry.value; | ||||
|  | ||||
|       var specialDayThisYear = DateTime(now.year, month, day); | ||||
|       var specialDayLastYear = DateTime(now.year - 1, month, day); | ||||
|  | ||||
|       if (specialDayThisYear.isBefore(now)) { | ||||
|         if (lastDate == null || specialDayThisYear.isAfter(lastDate)) { | ||||
|           lastDate = specialDayThisYear; | ||||
|           lastEvent = eventName; | ||||
|         } | ||||
|       } else if (specialDayLastYear.isBefore(now)) { | ||||
|         if (lastDate == null || specialDayLastYear.isAfter(lastDate)) { | ||||
|           lastDate = specialDayLastYear; | ||||
|           lastEvent = eventName; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (lastEvent != null && lastDate != null) { | ||||
|       return (lastEvent, lastDate); | ||||
|     } | ||||
|  | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   (String, DateTime)? getNextSpecialDay() { | ||||
|     final now = DateTime.now().toLocal(); | ||||
|     final birthday = _user.user?.profile?.birthday?.toLocal(); | ||||
|  | ||||
|     // Stored as key: month, day | ||||
|     final Map<String, (int, int)> specialDays = { | ||||
|       if (birthday != null) 'Birthday': (birthday.month, birthday.day), | ||||
|       ...kSpecialDays, | ||||
|     }; | ||||
|  | ||||
|     DateTime? closestDate; | ||||
|     String? closestEvent; | ||||
|  | ||||
|     for (final entry in specialDays.entries) { | ||||
|       final eventName = entry.key; | ||||
|       final (month, day) = entry.value; | ||||
|  | ||||
|       // Calculate the special day's DateTime in the current year | ||||
|       var specialDay = DateTime(now.year, month, day); | ||||
|  | ||||
|       // If the special day has already passed this year, consider it for the next year | ||||
|       if (specialDay.isBefore(now)) { | ||||
|         specialDay = DateTime(now.year + 1, month, day); | ||||
|       } | ||||
|  | ||||
|       // Check if this special day is closer than the previously found one | ||||
|       if (closestDate == null || specialDay.isBefore(closestDate)) { | ||||
|         closestDate = specialDay; | ||||
|         closestEvent = eventName; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (closestEvent != null && closestDate != null) { | ||||
|       return (closestEvent, closestDate); | ||||
|     } | ||||
|  | ||||
|     // No special day found | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   double getSpecialDayProgress(DateTime last, DateTime next) { | ||||
|     final totalDuration = next.difference(last).inSeconds.toDouble(); | ||||
|     final elapsedDuration = DateTime.now().difference(last).inSeconds.toDouble(); | ||||
|     return (elapsedDuration / totalDuration).clamp(0.0, 1.0); | ||||
|   } | ||||
| } | ||||
| @@ -34,7 +34,6 @@ class UserProvider extends ChangeNotifier { | ||||
|     refreshUser().then((value) { | ||||
|       if (value != null) { | ||||
|         log('Logged in as @${value.name}'); | ||||
|         _home.saveWidgetData('user', value.toJson()); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class HomeWidgetProvider { | ||||
|  | ||||
|   Future<void> initialize() async { | ||||
|     if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return; | ||||
|     if (!kIsWeb && Platform.isIOS) { | ||||
|     if (Platform.isIOS) { | ||||
|       await HomeWidget.setAppGroupId("group.solsynth.solian"); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -22,8 +22,9 @@ const Map<String, IconData> kCategoryIcons = { | ||||
|   'sports': Symbols.sports_soccer, | ||||
|   'music': Symbols.music_note, | ||||
|   'news': Symbols.newspaper, | ||||
|   'knowledge': Symbols.book, | ||||
|   'knowledge': Symbols.library_books, | ||||
|   'literature': Symbols.book, | ||||
|   'funny': Symbols.attractions, | ||||
| }; | ||||
|  | ||||
| class ExploreScreen extends StatefulWidget { | ||||
| @@ -184,26 +185,27 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|                 preferredSize: const Size.fromHeight(50), | ||||
|                 child: SizedBox( | ||||
|                   height: 50, | ||||
|                   child: ListView.builder( | ||||
|                     padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12), | ||||
|                   child: SingleChildScrollView( | ||||
|                     scrollDirection: Axis.horizontal, | ||||
|                     itemCount: _categories.length, | ||||
|                     itemBuilder: (context, idx) { | ||||
|                       final ele = _categories[idx]; | ||||
|                       return StyledWidget(ChoiceChip( | ||||
|                         avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark), | ||||
|                         label: Text( | ||||
|                           'postCategory${ele.alias.capitalize()}'.trExists() | ||||
|                               ? 'postCategory${ele.alias.capitalize()}'.tr() | ||||
|                               : ele.name, | ||||
|                         ), | ||||
|                         selected: _selectedCategory == ele.alias, | ||||
|                         onSelected: (value) { | ||||
|                           _selectedCategory = value ? ele.alias : null; | ||||
|                           _refreshPosts(); | ||||
|                         }, | ||||
|                       )).padding(horizontal: 4); | ||||
|                     }, | ||||
|                     padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12), | ||||
|                     child: Row( | ||||
|                       mainAxisAlignment: MainAxisAlignment.center, | ||||
|                       children: _categories.map((ele) { | ||||
|                         return StyledWidget(ChoiceChip( | ||||
|                           avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark), | ||||
|                           label: Text( | ||||
|                             'postCategory${ele.alias.capitalize()}'.trExists() | ||||
|                                 ? 'postCategory${ele.alias.capitalize()}'.tr() | ||||
|                                 : ele.name, | ||||
|                           ), | ||||
|                           selected: _selectedCategory == ele.alias, | ||||
|                           onSelected: (value) { | ||||
|                             _selectedCategory = value ? ele.alias : null; | ||||
|                             _refreshPosts(); | ||||
|                           }, | ||||
|                         )).padding(horizontal: 4); | ||||
|                       }).toList(), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|   | ||||
| @@ -11,11 +11,13 @@ import 'package:go_router/go_router.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:surface/providers/config.dart'; | ||||
| import 'package:surface/providers/post.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/special_day.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/providers/widget.dart'; | ||||
| import 'package:surface/types/check_in.dart'; | ||||
| @@ -79,8 +81,8 @@ class _HomeScreenState extends State<HomeScreen> { | ||||
|                 child: Column( | ||||
|                   mainAxisAlignment: constraints.maxWidth > 640 ? MainAxisAlignment.center : MainAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     _HomeDashSpecialDayWidget().padding(bottom: 8, horizontal: 8), | ||||
|                     _HomeDashUpdateWidget(padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8)), | ||||
|                     _HomeDashSpecialDayWidget().padding(horizontal: 8), | ||||
|                     StaggeredGrid.extent( | ||||
|                       maxCrossAxisExtent: 280, | ||||
|                       mainAxisSpacing: 8, | ||||
| @@ -156,36 +158,59 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ua = context.watch<UserProvider>(); | ||||
|     final today = DateTime.now(); | ||||
|     final birthday = ua.user?.profile?.birthday?.toLocal(); | ||||
|     final isBirthday = birthday != null && birthday.day == today.day && birthday.month == today.month; | ||||
|     final dayz = context.watch<SpecialDayProvider>(); | ||||
|  | ||||
|     return Column( | ||||
|       spacing: 8, | ||||
|       children: [ | ||||
|         if (isBirthday) | ||||
|           Card( | ||||
|             child: ListTile( | ||||
|               leading: Text('🎂').fontSize(24), | ||||
|               title: Text('happyBirthday').tr(args: [ua.user?.nick ?? 'user']), | ||||
|             ), | ||||
|           ).padding(bottom: 8), | ||||
|         if (today.month == 12 && today.day == 25) | ||||
|           Card( | ||||
|             child: ListTile( | ||||
|               leading: Text('🎄').fontSize(24), | ||||
|               title: Text('celebrateMerryXmas').tr(args: [ua.user?.nick ?? 'user']), | ||||
|             ), | ||||
|     final days = dayz.getSpecialDays(); | ||||
|  | ||||
|     if (days.isNotEmpty) { | ||||
|       return Column( | ||||
|           spacing: 8, | ||||
|           children: days.map((ele) { | ||||
|             return Card( | ||||
|               child: ListTile( | ||||
|                 leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24), | ||||
|                 title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']), | ||||
|                 subtitle: Text( | ||||
|                   DateFormat('y/M/d').format(DateTime.now().copyWith( | ||||
|                     month: kSpecialDays[ele]!.$1, | ||||
|                     day: kSpecialDays[ele]!.$2, | ||||
|                   )), | ||||
|                 ), | ||||
|               ), | ||||
|             ).padding(bottom: 8); | ||||
|           }).toList()); | ||||
|     } | ||||
|  | ||||
|     final nextOne = dayz.getNextSpecialDay(); | ||||
|     final lastOne = dayz.getLastSpecialDay(); | ||||
|  | ||||
|     if (nextOne != null && lastOne != null) { | ||||
|       var (name, date) = nextOne; | ||||
|       date = date.add(Duration(days: 1)); | ||||
|       final progress = dayz.getSpecialDayProgress(lastOne.$2, date); | ||||
|       final diff = nextOne.$2.add(-const Duration(days: 1)).difference(lastOne.$2); | ||||
|       return Card( | ||||
|         child: ListTile( | ||||
|           leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24), | ||||
|           title: Text('pending$name').tr(args: [RelativeTime(context).format(date)]), | ||||
|           subtitle: Row( | ||||
|             crossAxisAlignment: CrossAxisAlignment.center, | ||||
|             children: [ | ||||
|               Text('${diff.inDays}d · ${(progress * 100).toStringAsFixed(2)}%'), | ||||
|               const Gap(8), | ||||
|               Expanded( | ||||
|                 child: LinearProgressIndicator( | ||||
|                   value: progress, | ||||
|                   borderRadius: BorderRadius.circular(8), | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         if (today.month == 1 && today.day == 1) | ||||
|           Card( | ||||
|             child: ListTile( | ||||
|               leading: Text('🎉').fontSize(24), | ||||
|               title: Text('celebrateNewYear').tr(args: [ua.user?.nick ?? 'user']), | ||||
|             ), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
|         ), | ||||
|       ).padding(bottom: 8); | ||||
|     } | ||||
|  | ||||
|     return const SizedBox.shrink(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -493,9 +518,7 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati | ||||
|     setState(() => _isBusy = true); | ||||
|     try { | ||||
|       final pt = context.read<SnPostContentProvider>(); | ||||
|       final home = context.read<HomeWidgetProvider>(); | ||||
|       _posts = await pt.listRecommendations(); | ||||
|       home.saveWidgetData('post_featured', _posts!.first.toJson()); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
|   | ||||
| @@ -99,11 +99,16 @@ class _PostSearchScreenState extends State<PostSearchScreen> { | ||||
|         ], | ||||
|       ).padding(horizontal: 24, vertical: 16), | ||||
|     ).then((_) { | ||||
|       _posts.clear(); | ||||
|       _fetchPosts(); | ||||
|       _refreshPosts(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   Future<void> _refreshPosts() { | ||||
|     _postCount = null; | ||||
|     _posts.clear(); | ||||
|     return _fetchPosts(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     const labelShadows = <Shadow>[ | ||||
| @@ -144,8 +149,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> { | ||||
|                     setState(() => _posts[idx] = data); | ||||
|                   }, | ||||
|                   onDeleted: () { | ||||
|                     _posts.clear(); | ||||
|                     _fetchPosts(); | ||||
|                     _refreshPosts(); | ||||
|                   }, | ||||
|                 ), | ||||
|                 onTap: () { | ||||
| @@ -176,10 +180,8 @@ class _PostSearchScreenState extends State<PostSearchScreen> { | ||||
|                     _searchTerm = value; | ||||
|                   }, | ||||
|                   onSubmitted: (value) { | ||||
|                     setState(() => _posts.clear()); | ||||
|  | ||||
|                     _searchTerm = value; | ||||
|                     _fetchPosts(); | ||||
|                     _refreshPosts(); | ||||
|                   }, | ||||
|                 ), | ||||
|                 if (_lastTook != null) | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/types/chat.dart'; | ||||
| import 'package:surface/widgets/account/account_image.dart'; | ||||
| import 'package:surface/widgets/attachment/attachment_list.dart'; | ||||
| import 'package:surface/widgets/context_menu.dart'; | ||||
| import 'package:surface/widgets/link_preview.dart'; | ||||
| import 'package:surface/widgets/markdown_content.dart'; | ||||
| import 'package:swipe_to/swipe_to.dart'; | ||||
| @@ -53,7 +54,7 @@ class ChatMessage extends StatelessWidget { | ||||
|       swipeSensitivity: 20, | ||||
|       onLeftSwipe: onReply != null ? (_) => onReply!(data) : null, | ||||
|       onRightSwipe: onEdit != null ? (_) => onEdit!(data) : null, | ||||
|       child: ContextMenuRegion( | ||||
|       child: ContextMenuArea( | ||||
|         contextMenu: ContextMenu( | ||||
|           entries: [ | ||||
|             MenuHeader(text: "eventResourceTag".tr(args: ['#${data.id}'])), | ||||
|   | ||||
							
								
								
									
										47
									
								
								lib/widgets/context_menu.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								lib/widgets/context_menu.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:flutter_context_menu/flutter_context_menu.dart'; | ||||
| import 'package:responsive_framework/responsive_framework.dart'; | ||||
|  | ||||
| class ContextMenuArea extends StatelessWidget { | ||||
|   final ContextMenu contextMenu; | ||||
|   final Widget child; | ||||
|   final ValueChanged<dynamic>? onItemSelected; | ||||
|  | ||||
|   const ContextMenuArea({ | ||||
|     super.key, | ||||
|     required this.contextMenu, | ||||
|     required this.child, | ||||
|     this.onItemSelected, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     Offset mousePosition = Offset.zero; | ||||
|  | ||||
|     return Listener( | ||||
|       onPointerDown: (event) { | ||||
|         mousePosition = event.position; | ||||
|         final isCollapseDrawer = ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE); | ||||
|         if (!isCollapseDrawer) { | ||||
|           final isExpandDrawer = ResponsiveBreakpoints.of(context).largerThan(TABLET); | ||||
|           // Leave padding for side navigation | ||||
|           mousePosition = isExpandDrawer | ||||
|               ? mousePosition.copyWith(dx: mousePosition.dx - 304 * 2) | ||||
|               : mousePosition.copyWith(dx: mousePosition.dx - 72 * 2); | ||||
|         } | ||||
|       }, | ||||
|       child: GestureDetector( | ||||
|         onLongPress: () => _showMenu(context, mousePosition), | ||||
|         onSecondaryTap: () => _showMenu(context, mousePosition), | ||||
|         child: child, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void _showMenu(BuildContext context, Offset mousePosition) async { | ||||
|     final menu = contextMenu.copyWith(position: contextMenu.position ?? mousePosition); | ||||
|     final value = await showContextMenu(context, contextMenu: menu); | ||||
|     onItemSelected?.call(value); | ||||
|   } | ||||
| } | ||||
| @@ -14,6 +14,7 @@ import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/controllers/post_write_controller.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/widgets/attachment/attachment_zoom.dart'; | ||||
| import 'package:surface/widgets/context_menu.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
|  | ||||
| class PostMediaPendingList extends StatelessWidget { | ||||
| @@ -87,7 +88,7 @@ class PostMediaPendingList extends StatelessWidget { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ContextMenu _buildContextMenu(BuildContext context, int idx, PostWriteMedia media) { | ||||
|   ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) { | ||||
|     return ContextMenu( | ||||
|       entries: [ | ||||
|         if (media.attachment == null && onUpload != null) | ||||
| @@ -174,8 +175,8 @@ class PostMediaPendingList extends StatelessWidget { | ||||
|         children: [ | ||||
|           const Gap(8), | ||||
|           if (thumbnail != null) | ||||
|             ContextMenuRegion( | ||||
|               contextMenu: _buildContextMenu(context, -1, thumbnail!), | ||||
|             ContextMenuArea( | ||||
|               contextMenu: _createContextMenu(context, -1, thumbnail!), | ||||
|               child: Container( | ||||
|                 decoration: BoxDecoration( | ||||
|                   border: Border.all( | ||||
| @@ -224,8 +225,8 @@ class PostMediaPendingList extends StatelessWidget { | ||||
|               itemCount: attachments.length, | ||||
|               itemBuilder: (context, idx) { | ||||
|                 final media = attachments[idx]; | ||||
|                 return ContextMenuRegion( | ||||
|                   contextMenu: _buildContextMenu(context, idx, media), | ||||
|                 return ContextMenuArea( | ||||
|                   contextMenu: _createContextMenu(context, idx, media), | ||||
|                   child: Container( | ||||
|                     decoration: BoxDecoration( | ||||
|                       border: Border.all( | ||||
|   | ||||
| @@ -282,20 +282,6 @@ class _PostCategoriesFieldState extends State<PostCategoriesField> { | ||||
|                 : null, | ||||
|           ), | ||||
|           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|           onChanged: (value) { | ||||
|             for (final divider in kTagsDividers) { | ||||
|               if (value.endsWith(divider)) { | ||||
|                 final tagValue = value.substring(0, value.length - 1); | ||||
|                 if (tagValue.isEmpty) return; | ||||
|                 if (!_currentCategories.contains(tagValue)) { | ||||
|                   setState(() => _currentCategories.add(tagValue)); | ||||
|                 } | ||||
|                 controller.clear(); | ||||
|                 widget.onUpdate(_currentCategories); | ||||
|                 break; | ||||
|               } | ||||
|             } | ||||
|           }, | ||||
|           onSubmitted: (_) { | ||||
|             onSubmitted(); | ||||
|           }, | ||||
|   | ||||
							
								
								
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -753,10 +753,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_udid | ||||
|       sha256: be464dc5b1fb7ee894f6a32d65c086ca5e177fdcf9375ac08d77495b98150f84 | ||||
|       sha256: "166bee5989a58c66b8b62000ea65edccc7c8167bbafdbb08022638db330dd030" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.1" | ||||
|     version: "4.0.0" | ||||
|   flutter_web_plugins: | ||||
|     dependency: "direct main" | ||||
|     description: flutter | ||||
| @@ -766,10 +766,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_webrtc | ||||
|       sha256: "430859fb5b763d7556d06ef287cfca582e17d9a2dc36da26017f25a5c0b2523e" | ||||
|       sha256: "0e138a0a3bf6830c29c8439b17be0e222d0de27fa72f24e6aee4d34de72f22ef" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.12.4" | ||||
|     version: "0.12.5" | ||||
|   freezed: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
| @@ -934,10 +934,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: image_picker_android | ||||
|       sha256: fa8141602fde3f7e2f81dbf043613eb44dfa325fa0bcf93c0f142c9f7a2c193e | ||||
|       sha256: aa6f1280b670861ac45220cc95adc59bb6ae130259d36f980ccb62220dc5e59f | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.8.12+18" | ||||
|     version: "0.8.12+19" | ||||
|   image_picker_for_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1086,10 +1086,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: livekit_client | ||||
|       sha256: "7802b5de1cae2ee3439db730d24d31c6dcbce173c5e6db2fc5774039a290bc2d" | ||||
|       sha256: a3ff529fe6745ee40cdedcd021d81c4a6ad946dd495e782596f2856eeeabc739 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.2" | ||||
|     version: "2.3.3" | ||||
|   logging: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev | ||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||
| # In Windows, build-name is used as the major, minor, and patch parts | ||||
| # of the product and file versions while build-number is used as the build suffix. | ||||
| version: 2.1.1+38 | ||||
| version: 2.1.1+39 | ||||
|  | ||||
| environment: | ||||
|   sdk: ^3.5.4 | ||||
| @@ -80,7 +80,7 @@ dependencies: | ||||
|   firebase_core: ^3.8.0 | ||||
|   firebase_messaging: ^15.1.5 | ||||
|   firebase_analytics: ^11.3.5 | ||||
|   flutter_udid: ^3.0.0 | ||||
|   flutter_udid: ^4.0.0 | ||||
|   media_kit: ^1.1.11 | ||||
|   media_kit_video: ^1.2.5 | ||||
|   media_kit_libs_video: ^1.0.5 | ||||
|   | ||||
							
								
								
									
										221
									
								
								web/index.html
									
									
									
									
									
								
							
							
						
						
									
										221
									
								
								web/index.html
									
									
									
									
									
								
							| @@ -1,130 +1,133 @@ | ||||
| <!DOCTYPE html><html><head> | ||||
|   <!-- | ||||
|     If you are serving your web app in a path other than the root, change the | ||||
|     href value below to reflect the base path you are serving from. | ||||
| <!DOCTYPE html> | ||||
| <html lang="en" oncontextmenu="event.preventDefault();"> | ||||
| <head> | ||||
|     <!-- | ||||
|       If you are serving your web app in a path other than the root, change the | ||||
|       href value below to reflect the base path you are serving from. | ||||
|  | ||||
|     The path provided below has to start and end with a slash "/" in order for | ||||
|     it to work correctly. | ||||
|       The path provided below has to start and end with a slash "/" in order for | ||||
|       it to work correctly. | ||||
|  | ||||
|     For more details: | ||||
|     * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base | ||||
|       For more details: | ||||
|       * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base | ||||
|  | ||||
|     This is a placeholder for base href that will be replaced by the value of | ||||
|     the `--base-href` argument provided to `flutter build`. | ||||
|   --> | ||||
|   <base href="$FLUTTER_BASE_HREF"> | ||||
|       This is a placeholder for base href that will be replaced by the value of | ||||
|       the `--base-href` argument provided to `flutter build`. | ||||
|     --> | ||||
|     <base href="$FLUTTER_BASE_HREF"> | ||||
|  | ||||
|   <meta charset="UTF-8"> | ||||
|   <meta content="IE=Edge" http-equiv="X-UA-Compatible"> | ||||
|   <meta name="description" content="A new Flutter project."> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta content="IE=Edge" http-equiv="X-UA-Compatible"> | ||||
|     <meta name="description" content="A new Flutter project."> | ||||
|  | ||||
|   <!-- iOS meta tags & icons --> | ||||
|   <meta name="apple-mobile-web-app-capable" content="yes"> | ||||
|   <meta name="apple-mobile-web-app-status-bar-style" content="black"> | ||||
|   <meta name="apple-mobile-web-app-title" content="surface"> | ||||
|   <link rel="apple-touch-icon" href="icons/Icon-192.png"> | ||||
|     <!-- iOS meta tags & icons --> | ||||
|     <meta name="apple-mobile-web-app-capable" content="yes"> | ||||
|     <meta name="apple-mobile-web-app-status-bar-style" content="black"> | ||||
|     <meta name="apple-mobile-web-app-title" content="surface"> | ||||
|     <link rel="apple-touch-icon" href="icons/Icon-192.png"> | ||||
|  | ||||
|   <!-- Favicon --> | ||||
|   <link rel="icon" type="image/png" href="favicon.png"> | ||||
|     <!-- Favicon --> | ||||
|     <link rel="icon" type="image/png" href="favicon.png"> | ||||
|  | ||||
|   <title>Solian</title> | ||||
|   <link rel="manifest" href="manifest.json"> | ||||
|    | ||||
|    | ||||
|   <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> | ||||
|    | ||||
|    | ||||
|    | ||||
|    | ||||
|   <style id="splash-screen-style"> | ||||
|     html { | ||||
|       height: 100% | ||||
|     } | ||||
|     <title>Solian</title> | ||||
|     <link rel="manifest" href="manifest.json"> | ||||
|  | ||||
|     body { | ||||
|       margin: 0; | ||||
|       min-height: 100%; | ||||
|       background-color: #ffffff; | ||||
|           background-size: 100% 100%; | ||||
|     } | ||||
|  | ||||
|     .center { | ||||
|       margin: 0; | ||||
|       position: absolute; | ||||
|       top: 50%; | ||||
|       left: 50%; | ||||
|       -ms-transform: translate(-50%, -50%); | ||||
|       transform: translate(-50%, -50%); | ||||
|     } | ||||
|     <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" | ||||
|           name="viewport"> | ||||
|  | ||||
|     .contain { | ||||
|       display:block; | ||||
|       width:100%; height:100%; | ||||
|       object-fit: contain; | ||||
|     } | ||||
|  | ||||
|     .stretch { | ||||
|       display:block; | ||||
|       width:100%; height:100%; | ||||
|     } | ||||
|     <style id="splash-screen-style"> | ||||
|         html { | ||||
|           height: 100% | ||||
|         } | ||||
|  | ||||
|     .cover { | ||||
|       display:block; | ||||
|       width:100%; height:100%; | ||||
|       object-fit: cover; | ||||
|     } | ||||
|         body { | ||||
|           margin: 0; | ||||
|           min-height: 100%; | ||||
|           background-color: #ffffff; | ||||
|               background-size: 100% 100%; | ||||
|         } | ||||
|  | ||||
|     .bottom { | ||||
|       position: absolute; | ||||
|       bottom: 0; | ||||
|       left: 50%; | ||||
|       -ms-transform: translate(-50%, 0); | ||||
|       transform: translate(-50%, 0); | ||||
|     } | ||||
|         .center { | ||||
|           margin: 0; | ||||
|           position: absolute; | ||||
|           top: 50%; | ||||
|           left: 50%; | ||||
|           -ms-transform: translate(-50%, -50%); | ||||
|           transform: translate(-50%, -50%); | ||||
|         } | ||||
|  | ||||
|     .bottomLeft { | ||||
|       position: absolute; | ||||
|       bottom: 0; | ||||
|       left: 0; | ||||
|     } | ||||
|         .contain { | ||||
|           display:block; | ||||
|           width:100%; height:100%; | ||||
|           object-fit: contain; | ||||
|         } | ||||
|  | ||||
|     .bottomRight { | ||||
|       position: absolute; | ||||
|       bottom: 0; | ||||
|       right: 0; | ||||
|     } | ||||
|         .stretch { | ||||
|           display:block; | ||||
|           width:100%; height:100%; | ||||
|         } | ||||
|  | ||||
|     @media (prefers-color-scheme: dark) { | ||||
|       body { | ||||
|         background-color: #000000; | ||||
|           } | ||||
|     } | ||||
|   </style> | ||||
|   <script id="splash-screen-script"> | ||||
|     function removeSplashFromWeb() { | ||||
|       document.getElementById("splash")?.remove(); | ||||
|       document.getElementById("splash-branding")?.remove(); | ||||
|       document.body.style.background = "transparent"; | ||||
|     } | ||||
|   </script> | ||||
|         .cover { | ||||
|           display:block; | ||||
|           width:100%; height:100%; | ||||
|           object-fit: cover; | ||||
|         } | ||||
|  | ||||
|         .bottom { | ||||
|           position: absolute; | ||||
|           bottom: 0; | ||||
|           left: 50%; | ||||
|           -ms-transform: translate(-50%, 0); | ||||
|           transform: translate(-50%, 0); | ||||
|         } | ||||
|  | ||||
|         .bottomLeft { | ||||
|           position: absolute; | ||||
|           bottom: 0; | ||||
|           left: 0; | ||||
|         } | ||||
|  | ||||
|         .bottomRight { | ||||
|           position: absolute; | ||||
|           bottom: 0; | ||||
|           right: 0; | ||||
|         } | ||||
|  | ||||
|         @media (prefers-color-scheme: dark) { | ||||
|           body { | ||||
|             background-color: #000000; | ||||
|               } | ||||
|         } | ||||
|     </style> | ||||
|     <script id="splash-screen-script"> | ||||
|         function removeSplashFromWeb() { | ||||
|           document.getElementById("splash")?.remove(); | ||||
|           document.getElementById("splash-branding")?.remove(); | ||||
|           document.body.style.background = "transparent"; | ||||
|         } | ||||
|     </script> | ||||
| </head> | ||||
| <body> | ||||
|   <picture id="splash-branding"> | ||||
|     <source srcset="splash/img/branding-1x.png 1x, splash/img/branding-2x.png 2x, splash/img/branding-3x.png 3x, splash/img/branding-4x.png 4x" media="(prefers-color-scheme: light)"> | ||||
|     <source srcset="splash/img/branding-dark-1x.png 1x, splash/img/branding-dark-2x.png 2x, splash/img/branding-dark-3x.png 3x, splash/img/branding-dark-4x.png 4x" media="(prefers-color-scheme: dark)"> | ||||
| <picture id="splash-branding"> | ||||
|     <source srcset="splash/img/branding-1x.png 1x, splash/img/branding-2x.png 2x, splash/img/branding-3x.png 3x, splash/img/branding-4x.png 4x" | ||||
|             media="(prefers-color-scheme: light)"> | ||||
|     <source srcset="splash/img/branding-dark-1x.png 1x, splash/img/branding-dark-2x.png 2x, splash/img/branding-dark-3x.png 3x, splash/img/branding-dark-4x.png 4x" | ||||
|             media="(prefers-color-scheme: dark)"> | ||||
|     <img class="bottom" aria-hidden="true" src="splash/img/branding-1x.png" alt=""> | ||||
|   </picture> | ||||
|   <picture id="splash"> | ||||
|       <source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)"> | ||||
|       <source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)"> | ||||
|       <img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt=""> | ||||
|   </picture> | ||||
|    | ||||
|    | ||||
|    | ||||
|    | ||||
|    | ||||
|   <script src="flutter_bootstrap.js" async=""></script> | ||||
| </picture> | ||||
| <picture id="splash"> | ||||
|     <source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" | ||||
|             media="(prefers-color-scheme: light)"> | ||||
|     <source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" | ||||
|             media="(prefers-color-scheme: dark)"> | ||||
|     <img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt=""> | ||||
| </picture> | ||||
|  | ||||
|  | ||||
| </body></html> | ||||
| <script src="flutter_bootstrap.js" async=""></script> | ||||
|  | ||||
|  | ||||
| </body> | ||||
| </html> | ||||
		Reference in New Issue
	
	Block a user