Compare commits
	
		
			7 Commits
		
	
	
		
			3.1.0+122
			...
			c061ef2132
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c061ef2132 | |||
| c378309bdd | |||
| b2c5d64fc5 | |||
|  | 5371637b16 | ||
| c5cbf0af37 | |||
| 1a31e22450 | |||
|  | 49db54529d | 
| @@ -51,6 +51,12 @@ android { | ||||
|     buildTypes { | ||||
|         release { | ||||
|             signingConfig = signingConfigs.getByName("release") | ||||
|  | ||||
|             isMinifyEnabled = true | ||||
|             proguardFiles( | ||||
|                 getDefaultProguardFile("proguard-android-optimize.txt"), | ||||
|                 "proguard-rules.pro" | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -58,7 +64,7 @@ android { | ||||
| dependencies { | ||||
|     implementation("com.google.android.material:material:1.12.0") | ||||
|     implementation("com.github.bumptech.glide:glide:4.16.0") | ||||
|     implementation("com.squareup.okhttp3:okhttp:4.12.0") | ||||
|     implementation("com.squareup.okhttp3:okhttp:5.1.0") | ||||
| } | ||||
|  | ||||
| flutter { | ||||
|   | ||||
							
								
								
									
										5
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # JNI Zero initialization (required for WebRTC native method registration) | ||||
| -keep class livekit.org.jni_zero.JniInit { | ||||
|     # Keep the init method un-obfuscated for native code callback | ||||
|     private static java.lang.Object[] init(); | ||||
| } | ||||
| @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip | ||||
|   | ||||
| @@ -18,11 +18,11 @@ pluginManagement { | ||||
|  | ||||
| plugins { | ||||
|     id("dev.flutter.flutter-plugin-loader") version "1.0.0" | ||||
|     id("com.android.application") version "8.10.1" apply false | ||||
|     id("com.android.application") version "8.12.0" apply false | ||||
|     // START: FlutterFire Configuration | ||||
|     id("com.google.gms.google-services") version("4.3.15") apply false | ||||
|     // END: FlutterFire Configuration | ||||
|     id("org.jetbrains.kotlin.android") version "1.8.22" apply false | ||||
|     id("org.jetbrains.kotlin.android") version("2.2.0") apply false | ||||
| } | ||||
|  | ||||
| include(":app") | ||||
|   | ||||
| @@ -789,5 +789,6 @@ | ||||
|   "linkKey": "Link Name", | ||||
|   "linkValue": "URL", | ||||
|   "debugOptions": "Debug Options", | ||||
|   "joinedAt": "Joined at {}" | ||||
|   "joinedAt": "Joined at {}", | ||||
|   "searchAccounts": "Search accounts..." | ||||
| } | ||||
|   | ||||
| @@ -46,7 +46,7 @@ | ||||
|     "delete": "删除", | ||||
|     "deletePublisher": "删除发布者", | ||||
|     "deletePublisherHint": "确定要删除此发布者吗?这也会删除此发布者下的所有帖子和收藏。", | ||||
|   "somethingWentWrong": "发生了一些错误", | ||||
|     "somethingWentWrong": "发生了一些错误……", | ||||
|     "deletePost": "删除帖子", | ||||
|     "deletePostHint": "确定要删除这篇帖子吗?", | ||||
|     "copyLink": "复制链接", | ||||
| @@ -120,14 +120,9 @@ | ||||
|         "other": "{}个附件" | ||||
|     }, | ||||
|     "edited": "已编辑", | ||||
|   "editedAt": "编辑于 {}", | ||||
|     "addVideo": "添加视频", | ||||
|     "addPhoto": "添加照片", | ||||
|     "addFile": "添加文件", | ||||
|   "addAttachmentById": "通过 ID 添加附件", | ||||
|   "enterFileId": "输入文件 ID", | ||||
|   "fileIdCannotBeEmpty": "文件 ID 不能为空", | ||||
|   "failedToFetchFile": "获取文件失败: {}", | ||||
|     "createDirectMessage": "创建新私人消息", | ||||
|     "gotoDirectMessage": "前往私信", | ||||
|     "react": "反应", | ||||
| @@ -350,7 +345,7 @@ | ||||
|     "accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。", | ||||
|     "unauthorized": "未授权", | ||||
|     "unauthorizedHint": "您未登录或会话已过期,请重新登录。", | ||||
|   "publisherBelongsTo": "属于 {}", | ||||
|     "publisherBelongsTo": "属于", | ||||
|     "postContent": "内容", | ||||
|     "postSettings": "设置", | ||||
|     "postPublisherUnselected": "未指定发布者", | ||||
| @@ -495,20 +490,29 @@ | ||||
|     "paymentError": "付款失败: {error}", | ||||
|     "usePinInstead": "使用 PIN 码", | ||||
|     "levelProgress": "等级进度", | ||||
|     "unlockedFeatures": "已解锁的功能", | ||||
|     "unlockedFeaturesDescription": "在您当前级别上解锁的功能将显示在这里。", | ||||
|     "stellarMembership": "恒星计划", | ||||
|     "upgradeYourPlan": "升级您的计划", | ||||
|     "chooseYourPlan": "选择你的方案", | ||||
|     "currentMembership": "当前:{}", | ||||
|   "currentMembershipMember": "恒星计划「{}」级会员", | ||||
|     "membershipExpires": "过期于:{}", | ||||
|     "membershipTierStellar": "恒星", | ||||
|     "membershipTierNova": "新星", | ||||
|     "membershipTierSupernova": "超新星", | ||||
|     "membershipTierUnknown": "未知", | ||||
|   "membershipPriceStellar": "每月 1200 源点,至少需要 3 级", | ||||
|   "membershipPriceNova": "每月 2400 源点,至少需要 6 级", | ||||
|   "membershipPriceSupernova": "每月 3600 源点,至少需要 9 级", | ||||
|     "membershipPriceStellar": "每月 10 金点", | ||||
|     "membershipPriceNova": "每月 20 金点", | ||||
|     "membershipPriceSupernova": "每月 30 金点", | ||||
|     "membershipFeatureBasic": "基础功能", | ||||
|     "membershipFeaturePrioritySupport": "优先支持", | ||||
|     "membershipFeatureAdFree": "无广告", | ||||
|     "membershipFeatureAllPrimary": "所有主要功能", | ||||
|     "membershipFeatureAdvancedCustomization": "高级自定义", | ||||
|     "membershipFeatureEarlyAccess": "抢先体验", | ||||
|     "membershipFeatureAllNova": "所有「新星」功能", | ||||
|     "membershipFeatureExclusiveContent": "限定内容", | ||||
|     "membershipFeatureVipSupport": "VIP 支持", | ||||
|     "membershipCurrentBadge": "当前", | ||||
|     "restorePurchase": "恢复购买", | ||||
|     "restorePurchaseDescription": "输入您付款的提供商和订单 ID 以恢复您的购买。", | ||||
| @@ -518,32 +522,161 @@ | ||||
|     "enterOrderId": "输入您的订单 ID", | ||||
|     "restore": "恢复", | ||||
|     "keyboardShortcuts": "键盘快捷键", | ||||
|   "about": "关于", | ||||
|   "membershipCancel": "取消会员订阅", | ||||
|   "membershipCancelConfirm": "您确定要取消您的会员订阅?", | ||||
|   "membershipCancelHint": "您确定要取消您的会员订阅吗?您将不会再被收费。您的会员资格将在当前计费周期结束前保持有效。并且您在当前订阅结束之前无法重新订阅。", | ||||
|   "membershipCancelSuccess": "您的会员订阅已成功取消。", | ||||
|   "aboutScreenTitle": "关于", | ||||
|   "aboutScreenVersionInfo": "版本 {} ({})", | ||||
|   "aboutScreenAppInfoSectionTitle": "应用信息", | ||||
|   "aboutScreenPackageNameLabel": "包名", | ||||
|   "aboutScreenVersionLabel": "版本", | ||||
|   "aboutScreenBuildNumberLabel": "构建编号", | ||||
|   "aboutScreenLinksSectionTitle": "链接", | ||||
|   "aboutScreenPrivacyPolicyTitle": "隐私政策", | ||||
|   "aboutScreenTermsOfServiceTitle": "服务条款", | ||||
|   "aboutScreenOpenSourceLicensesTitle": "开源许可证", | ||||
|   "aboutScreenDeveloperSectionTitle": "开发者", | ||||
|   "aboutScreenContactUsTitle": "联系我们", | ||||
|   "aboutScreenLicenseTitle": "许可证", | ||||
|   "aboutScreenLicenseContent": "GNU Affero General Public License v3.0", | ||||
|   "aboutScreenCopyright": "版权所有 © 索尔辛茨 {}", | ||||
|   "aboutScreenMadeWith": "由 Solar Network Team 用 ❤︎️ 制作", | ||||
|   "aboutScreenFailedToLoadPackageInfo": "加载包信息失败:{error}", | ||||
|   "copiedToClipboard": "已复制到剪贴板", | ||||
|   "copyToClipboardTooltip": "复制到剪贴板", | ||||
|   "postForwardingTo": "转发给", | ||||
|   "postReplyingTo": "回复给", | ||||
|   "postEditing": "您正在编辑现有帖子", | ||||
|   "postArticle": "文章" | ||||
|     "safetyReport": "举报", | ||||
|     "safetyReportTitle": "举报", | ||||
|     "safetyReportDescription": "通过举报不合适的内容和行为来维护我们社区的稳定。", | ||||
|     "safetyReportType": "举报类型", | ||||
|     "safetyReportReason": "更多证据", | ||||
|     "safetyReportReasonHint": "请提供更多证据……", | ||||
|     "safetyReportSubmit": "提交举报", | ||||
|     "safetyReportSubmitting": "提交中……", | ||||
|     "safetyReportSuccess": "举报成功,感谢您参与维护社区健康发展。", | ||||
|     "safetyReportError": "举报失败,请稍后重试。", | ||||
|     "safetyReportReasonRequired": "请提供举报证据", | ||||
|     "safetyReportTypeSpam": "垃圾或导向错误", | ||||
|     "safetyReportTypeHarassment": "骚扰或暴力行为", | ||||
|     "safetyReportTypeHateSpeech": "歧视言论", | ||||
|     "safetyReportTypeViolence": "威胁或暴力内容", | ||||
|     "safetyReportTypeAdultContent": "成人内容", | ||||
|     "safetyReportTypeIntellectualProperty": "抄袭", | ||||
|     "safetyReportTypeOther": "其它", | ||||
|     "safetyReportTypeInappropriate": "不良内容", | ||||
|     "safetyReportTypeCopyright": "版权侵害", | ||||
|     "safetyReportSuccessTitle": "举报成功", | ||||
|     "safetyReportErrorTitle": "错误", | ||||
|     "discover": "发现", | ||||
|     "joinRealm": "加入领域", | ||||
|     "removePublisherMember": "移除发布者", | ||||
|     "removePublisherMemberHint": "你确定要将这个成员从发布者中移除?", | ||||
|     "drafts": "草稿箱", | ||||
|     "noDrafts": "无草稿", | ||||
|     "articleDrafts": "文章草稿", | ||||
|     "postDrafts": "帖子草稿", | ||||
|     "saveDraft": "保存草稿", | ||||
|     "draftSaved": "草稿已保存", | ||||
|     "draftSaveFailed": "保存草稿失败", | ||||
|     "clearAllDrafts": "清除全部草稿", | ||||
|     "clearAllDraftsConfirm": "你确定要清除全部草稿?这一操作无法撤销。", | ||||
|     "clearAll": "清除所有", | ||||
|     "untitled": "未命名", | ||||
|     "noContent": "内容为空", | ||||
|     "justNow": "刚刚", | ||||
|     "minutesAgo": "{} 分钟以前", | ||||
|     "hoursAgo": "{} 小时以前", | ||||
|     "daysAgo": "{} 天以前", | ||||
|     "public": "公开的", | ||||
|     "unlisted": "不列出", | ||||
|     "friends": "朋友", | ||||
|     "selected": "选择的", | ||||
|     "private": "私密的", | ||||
|     "postContentEmpty": "发布的内容不能为空", | ||||
|     "share": "分享", | ||||
|     "sharePost": "分享帖子", | ||||
|     "quickActions": "快捷操作", | ||||
|     "post": "帖子", | ||||
|     "copy": "复制", | ||||
|     "sendToChat": "发送到聊天", | ||||
|     "failedToShareToPost": "分享到帖子失败:{}", | ||||
|     "shareToChatComingSoon": "分享到聊天的功能即将到来", | ||||
|     "failedToShareToChat": "分享到聊天失败:{}", | ||||
|     "shareToSpecificChatComingSoon": "分享到 {} 的功能即将到来", | ||||
|     "directChat": "私信", | ||||
|     "systemShareComingSoon": "系统分享功能即将到来", | ||||
|     "failedToShareToSystem": "分享到系统失败:{}", | ||||
|     "failedToCopy": "复制失败:{}", | ||||
|     "noChatRoomsAvailable": "没有聊天室可用", | ||||
|     "failedToLoadChats": "加载聊天室失败", | ||||
|     "contentToShare": "要分享的内容:", | ||||
|     "unknownChat": "未知聊天室", | ||||
|     "addAdditionalMessage": "添加额外消息……", | ||||
|     "uploadingFiles": "上传文件中……", | ||||
|     "sharedSuccessfully": "分享成功!", | ||||
|     "shareSuccess": "分享成功!", | ||||
|     "shareToSpecificChatSuccess": "分享到 {} 成功!", | ||||
|     "wouldYouLikeToGoToChat": "你想要前往聊天页面吗?", | ||||
|     "no": "是", | ||||
|     "yes": "否", | ||||
|     "navigateToChat": "前往聊天室", | ||||
|     "wouldYouLikeToNavigateToChat": "你想要前往聊天页面吗?", | ||||
|     "abuseReport": "举报", | ||||
|     "abuseReportTitle": "举报内容", | ||||
|     "abuseReportDescription": "通过举报不合适的内容和行为来帮助我们维护社区的健康稳定发展。", | ||||
|     "abuseReportType": "举报类型", | ||||
|     "abuseReportReason": "额外细节", | ||||
|     "abuseReportReasonHint": "请提供更多关于此的细节……", | ||||
|     "abuseReportSubmit": "提交举报", | ||||
|     "abuseReportSuccess": "举报提交成功,感谢你为社区维护作出贡献。", | ||||
|     "abuseReportError": "无法提交举报,请稍后再试。", | ||||
|     "abuseReportReasonRequired": "请提供关于此事件的细节", | ||||
|     "abuseReportSuccessTitle": "举报已提交", | ||||
|     "abuseReportErrorTitle": "错误", | ||||
|     "abuseReportTypeSpam": "垃圾或错误信息", | ||||
|     "abuseReportTypeHarassment": "骚扰或滥用", | ||||
|     "abuseReportTypeInappropriate": "不合适的内容", | ||||
|     "abuseReportTypeViolence": "暴力或人身威胁", | ||||
|     "abuseReportTypeCopyright": "版权侵犯", | ||||
|     "abuseReportTypeImpersonation": "冒充", | ||||
|     "abuseReportTypeOffensiveContent": "冒犯性内容", | ||||
|     "abuseReportTypePrivacyViolation": "隐私侵犯", | ||||
|     "abuseReportTypeIllegalContent": "违法内容", | ||||
|     "abuseReportTypeOther": "其他", | ||||
|     "tags": "标签", | ||||
|     "tagsHint": "输入标签,用英文逗号分隔", | ||||
|     "categories": "分类", | ||||
|     "categoriesHint": "输入分类,由逗号隔开", | ||||
|     "chatNotJoined": "你还没有加入这个聊天。", | ||||
|     "chatUnableJoin": "由于该聊天的访问设置使你无法加入。", | ||||
|     "chatJoin": "加入聊天", | ||||
|     "realmJoin": "加入领域", | ||||
|     "realmJoinSuccess": "成功加入领域。", | ||||
|     "discoverRealms": "发现领域", | ||||
|     "discoverPublishers": "发现开发者", | ||||
|     "search": "搜索", | ||||
|     "publisherMembers": "合作者", | ||||
|     "developerHub": "开发者中心", | ||||
|     "developerHubUnselectedHint": "选择一名开发者查看总结数据或成为一名。", | ||||
|     "enrollDeveloper": "成为一名开发者", | ||||
|     "enrollDeveloperHint": "让你的一个发布者成为开发者。", | ||||
|     "noPublishersToEnroll": "你没有可以成为开发者的发布者。", | ||||
|     "totalCustomApps": "所有应用套件", | ||||
|     "customApps": "应用套件", | ||||
|     "noCustomApps": "还没有应用套件。", | ||||
|     "createCustomApp": "创建应用套件", | ||||
|     "editCustomApp": "编辑应用套件", | ||||
|     "deleteCustomApp": "删除应用套件", | ||||
|     "deleteCustomAppHint": "你确定要删除这个应用套件吗?这一步无法撤销。", | ||||
|     "publicRealm": "公开领域", | ||||
|     "publicRealmDescription": "所有人都可以预览这个领域的内容。", | ||||
|     "communityRealm": "领域", | ||||
|     "communityRealmDescription": "所有人都可以加入该领域并参与讨论,并将在发现和反馈页面显示。", | ||||
|     "publicChat": "公开聊天", | ||||
|     "publicChatDescription": "任何人都可以预览此聊天的内容。包括未加入的机器人。", | ||||
|     "communityChat": "社区聊天", | ||||
|     "communityChatDescription": "所有人都可以加入该聊天并参与参与讨论。", | ||||
|     "appLinks": "应用链接", | ||||
|     "homePageUrl": "主页链接", | ||||
|     "privacyPolicyUrl": "隐私政策链接", | ||||
|     "termsOfServiceUrl": "用户协议链接", | ||||
|     "oauthConfig": "OAuth 配置", | ||||
|     "clientUri": "客户端 URI", | ||||
|     "redirectUris": "重定向 URIs", | ||||
|     "addRedirectUri": "添加重定向 URI", | ||||
|     "allowedScopes": "允许的范围", | ||||
|     "requirePkce": "需要 PKCE", | ||||
|     "allowOfflineAccess": "允许离线访问", | ||||
|     "redirectUri": "重定向 URI", | ||||
|     "redirectUriHint": "重定向 URI 用于 OAuth 认证,但您的项目状态转为线上时我们会验证请求中的重定向 URI 是否符合此配置。", | ||||
|     "uriRequired": "这个 URI 是必须填写的。", | ||||
|     "uriInvalid": "无效 URI。", | ||||
|     "add": "添加", | ||||
|     "addScope": "添加范围", | ||||
|     "scope": "范围", | ||||
|     "publisherFeatures": "功能", | ||||
|     "publisherFeatureDevelop": "开发者计划", | ||||
|     "publisherFeatureDevelopDescription": "为你的开发者解锁包括应用套件,API 及更多开发功能。", | ||||
|     "publisherFeatureDevelopHint": "目前该功能还在开发中,你需要邀请才可解锁。", | ||||
|     "learnMore": "了解更多", | ||||
|     "discoverWebArticles": "来自站外的文章", | ||||
|     "webArticlesStand": "文章亭", | ||||
|     "about": "关于" | ||||
| } | ||||
| @@ -28,6 +28,7 @@ import 'package:relative_time/relative_time.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | ||||
| import 'package:flutter_native_splash/flutter_native_splash.dart'; | ||||
| import 'package:island/widgets/keyboard_navigation.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
| import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; | ||||
|  | ||||
| @@ -244,7 +245,8 @@ class IslandApp extends HookConsumerWidget { | ||||
|  | ||||
|     final router = ref.watch(routerProvider); | ||||
|  | ||||
|     return MaterialApp.router( | ||||
|     return KeyboardNavigation( | ||||
|       child: MaterialApp.router( | ||||
|         theme: theme?.light, | ||||
|         darkTheme: theme?.dark, | ||||
|         themeMode: ThemeMode.system, | ||||
| @@ -268,6 +270,7 @@ class IslandApp extends HookConsumerWidget { | ||||
|             ], | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -216,6 +216,7 @@ class RelationshipScreen extends HookConsumerWidget { | ||||
|       final result = await showModalBottomSheet( | ||||
|         context: context, | ||||
|         useRootNavigator: true, | ||||
|         isScrollControlled: true, | ||||
|         builder: (context) => AccountPickerSheet(), | ||||
|       ); | ||||
|       if (result == null) return; | ||||
|   | ||||
| @@ -227,6 +227,7 @@ class ChatListScreen extends HookConsumerWidget { | ||||
|       final result = await showModalBottomSheet( | ||||
|         context: context, | ||||
|         useRootNavigator: true, | ||||
|         isScrollControlled: true, | ||||
|         builder: (context) => const AccountPickerSheet(), | ||||
|       ); | ||||
|       if (result == null) return; | ||||
|   | ||||
| @@ -589,6 +589,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | ||||
|       final result = await showModalBottomSheet( | ||||
|         context: context, | ||||
|         useRootNavigator: true, | ||||
|         isScrollControlled: true, | ||||
|         builder: (context) => const AccountPickerSheet(), | ||||
|       ); | ||||
|       if (result == null) return; | ||||
| @@ -727,7 +728,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | ||||
|                                       apiClientProvider, | ||||
|                                     ); | ||||
|                                     await apiClient.delete( | ||||
|                                       '/chat/$roomId/members/${member.accountId}', | ||||
|                                       '/sphere/chat/$roomId/members/${member.accountId}', | ||||
|                                     ); | ||||
|                                     // Refresh both providers | ||||
|                                     memberNotifier.reset(); | ||||
|   | ||||
| @@ -708,6 +708,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | ||||
|  | ||||
|     Future<void> invitePerson() async { | ||||
|       final result = await showModalBottomSheet( | ||||
|         useRootNavigator: true, | ||||
|         isScrollControlled: true, | ||||
|         context: context, | ||||
|         builder: (context) => const AccountPickerSheet(), | ||||
|   | ||||
| @@ -180,6 +180,7 @@ class StickerPackDetailScreen extends HookConsumerWidget { | ||||
|                                               .pushNamed( | ||||
|                                                 'creatorStickerEdit', | ||||
|                                                 pathParameters: { | ||||
|                                                   'name': pubName, | ||||
|                                                   'packId': id, | ||||
|                                                   'id': sticker.id, | ||||
|                                                 }, | ||||
|   | ||||
| @@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget { | ||||
|               context | ||||
|                   .pushNamed( | ||||
|                     'creatorStickerPackNew', | ||||
|                     queryParameters: {'name': pubName}, | ||||
|                     pathParameters: {'name': pubName}, | ||||
|                   ) | ||||
|                   .then((value) { | ||||
|                     if (value != null) { | ||||
| @@ -187,10 +187,8 @@ class EditStickerPacksScreen extends HookConsumerWidget { | ||||
|             'description': descriptionController.text, | ||||
|             'prefix': prefixController.text, | ||||
|           }, | ||||
|           options: Options( | ||||
|             method: packId == null ? 'POST' : 'PATCH', | ||||
|             headers: {'X-Pub': pubName}, | ||||
|           ), | ||||
|           queryParameters: {'pub': pubName}, | ||||
|           options: Options(method: packId == null ? 'POST' : 'PATCH'), | ||||
|         ); | ||||
|         if (!context.mounted) return; | ||||
|         context.pop(SnStickerPack.fromJson(resp.data)); | ||||
|   | ||||
| @@ -488,6 +488,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | ||||
|     Future<void> invitePerson() async { | ||||
|       final result = await showModalBottomSheet( | ||||
|         isScrollControlled: true, | ||||
|         useRootNavigator: true, | ||||
|         context: context, | ||||
|         builder: (context) => const AccountPickerSheet(), | ||||
|       ); | ||||
|   | ||||
| @@ -67,6 +67,9 @@ Future<void> subscribePushNotification( | ||||
|   Dio apiClient, { | ||||
|   bool detailedErrors = false, | ||||
| }) async { | ||||
|   if (Platform.isLinux){ | ||||
|     return; | ||||
|   } | ||||
|   await FirebaseMessaging.instance.requestPermission( | ||||
|     alert: true, | ||||
|     badge: true, | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'dart:async'; | ||||
|  | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -44,9 +45,8 @@ class AccountPickerSheet extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     return Container( | ||||
|       constraints: BoxConstraints( | ||||
|         maxHeight: MediaQuery.of(context).size.height * 0.4, | ||||
|       ), | ||||
|       padding: MediaQuery.of(context).viewInsets, | ||||
|       height: MediaQuery.of(context).size.height * 0.6, | ||||
|       child: Column( | ||||
|         children: [ | ||||
|           Padding( | ||||
| @@ -54,8 +54,8 @@ class AccountPickerSheet extends HookConsumerWidget { | ||||
|             child: TextField( | ||||
|               controller: searchController, | ||||
|               onChanged: onSearchChanged, | ||||
|               decoration: const InputDecoration( | ||||
|                 hintText: 'Search accounts...', | ||||
|               decoration: InputDecoration( | ||||
|                 hintText: 'searchAccounts'.tr(), | ||||
|                 contentPadding: EdgeInsets.symmetric( | ||||
|                   horizontal: 18, | ||||
|                   vertical: 16, | ||||
|   | ||||
							
								
								
									
										86
									
								
								lib/widgets/keyboard_navigation.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/widgets/keyboard_navigation.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
|  | ||||
| enum VimMode { normal, insert } | ||||
|  | ||||
| class KeyboardNavigation extends StatefulWidget { | ||||
|   const KeyboardNavigation({super.key, required this.child}); | ||||
|  | ||||
|   final Widget child; | ||||
|  | ||||
|   @override | ||||
|   State<KeyboardNavigation> createState() => _KeyboardNavigationState(); | ||||
| } | ||||
|  | ||||
| class _KeyboardNavigationState extends State<KeyboardNavigation> { | ||||
|   VimMode _mode = VimMode.normal; | ||||
|   final FocusScopeNode _focusScopeNode = FocusScopeNode(); | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _focusScopeNode.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) { | ||||
|     if (event is! KeyDownEvent && event is! KeyRepeatEvent) { | ||||
|       return KeyEventResult.ignored; | ||||
|     } | ||||
|  | ||||
|     if (_mode == VimMode.normal) { | ||||
|       if (event.logicalKey == LogicalKeyboardKey.keyJ) { | ||||
|         node.focusInDirection(TraversalDirection.down); | ||||
|         return KeyEventResult.handled; | ||||
|       } else if (event.logicalKey == LogicalKeyboardKey.keyK) { | ||||
|         node.focusInDirection(TraversalDirection.up); | ||||
|         return KeyEventResult.handled; | ||||
|       } else if (event.logicalKey == LogicalKeyboardKey.keyH) { | ||||
|         final focusNode = FocusManager.instance.primaryFocus; | ||||
|         if (focusNode != null) { | ||||
|           final scrollable = Scrollable.of(focusNode.context!); | ||||
|           if (scrollable.position.axis == Axis.horizontal) { | ||||
|             scrollable.position.moveTo(scrollable.position.pixels - 50); | ||||
|             return KeyEventResult.handled; | ||||
|           } | ||||
|         } | ||||
|         node.focusInDirection(TraversalDirection.left); | ||||
|         return KeyEventResult.handled; | ||||
|       } else if (event.logicalKey == LogicalKeyboardKey.keyL) { | ||||
|         final focusNode = FocusManager.instance.primaryFocus; | ||||
|         if (focusNode != null) { | ||||
|           final scrollable = Scrollable.of(focusNode.context!); | ||||
|           if (scrollable.position.axis == Axis.horizontal) { | ||||
|             scrollable.position.moveTo(scrollable.position.pixels + 50); | ||||
|             return KeyEventResult.handled; | ||||
|           } | ||||
|         } | ||||
|         node.focusInDirection(TraversalDirection.right); | ||||
|         return KeyEventResult.handled; | ||||
|       } else if (event.logicalKey == LogicalKeyboardKey.keyI) { | ||||
|         setState(() { | ||||
|           _mode = VimMode.insert; | ||||
|         }); | ||||
|         return KeyEventResult.handled; | ||||
|       } | ||||
|     } else if (_mode == VimMode.insert) { | ||||
|       if (event.logicalKey == LogicalKeyboardKey.escape) { | ||||
|         setState(() { | ||||
|           _mode = VimMode.normal; | ||||
|         }); | ||||
|         // Unfocus the current widget to prevent typing | ||||
|         node.unfocus(); | ||||
|         return KeyEventResult.handled; | ||||
|       } | ||||
|     } | ||||
|     return KeyEventResult.ignored; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Focus( | ||||
|       focusNode: _focusScopeNode, | ||||
|       onKeyEvent: _handleKeyEvent, | ||||
|       child: widget.child, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user