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 { |     buildTypes { | ||||||
|         release { |         release { | ||||||
|             signingConfig = signingConfigs.getByName("release") |             signingConfig = signingConfigs.getByName("release") | ||||||
|  |  | ||||||
|  |             isMinifyEnabled = true | ||||||
|  |             proguardFiles( | ||||||
|  |                 getDefaultProguardFile("proguard-android-optimize.txt"), | ||||||
|  |                 "proguard-rules.pro" | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -58,7 +64,7 @@ android { | |||||||
| dependencies { | dependencies { | ||||||
|     implementation("com.google.android.material:material:1.12.0") |     implementation("com.google.android.material:material:1.12.0") | ||||||
|     implementation("com.github.bumptech.glide:glide:4.16.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 { | 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 | distributionPath=wrapper/dists | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | 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 { | plugins { | ||||||
|     id("dev.flutter.flutter-plugin-loader") version "1.0.0" |     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 |     // START: FlutterFire Configuration | ||||||
|     id("com.google.gms.google-services") version("4.3.15") apply false |     id("com.google.gms.google-services") version("4.3.15") apply false | ||||||
|     // END: FlutterFire Configuration |     // 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") | include(":app") | ||||||
|   | |||||||
| @@ -789,5 +789,6 @@ | |||||||
|   "linkKey": "Link Name", |   "linkKey": "Link Name", | ||||||
|   "linkValue": "URL", |   "linkValue": "URL", | ||||||
|   "debugOptions": "Debug Options", |   "debugOptions": "Debug Options", | ||||||
|   "joinedAt": "Joined at {}" |   "joinedAt": "Joined at {}", | ||||||
|  |   "searchAccounts": "Search accounts..." | ||||||
| } | } | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ | |||||||
|     "delete": "删除", |     "delete": "删除", | ||||||
|     "deletePublisher": "删除发布者", |     "deletePublisher": "删除发布者", | ||||||
|     "deletePublisherHint": "确定要删除此发布者吗?这也会删除此发布者下的所有帖子和收藏。", |     "deletePublisherHint": "确定要删除此发布者吗?这也会删除此发布者下的所有帖子和收藏。", | ||||||
|   "somethingWentWrong": "发生了一些错误", |     "somethingWentWrong": "发生了一些错误……", | ||||||
|     "deletePost": "删除帖子", |     "deletePost": "删除帖子", | ||||||
|     "deletePostHint": "确定要删除这篇帖子吗?", |     "deletePostHint": "确定要删除这篇帖子吗?", | ||||||
|     "copyLink": "复制链接", |     "copyLink": "复制链接", | ||||||
| @@ -120,14 +120,9 @@ | |||||||
|         "other": "{}个附件" |         "other": "{}个附件" | ||||||
|     }, |     }, | ||||||
|     "edited": "已编辑", |     "edited": "已编辑", | ||||||
|   "editedAt": "编辑于 {}", |  | ||||||
|     "addVideo": "添加视频", |     "addVideo": "添加视频", | ||||||
|     "addPhoto": "添加照片", |     "addPhoto": "添加照片", | ||||||
|     "addFile": "添加文件", |     "addFile": "添加文件", | ||||||
|   "addAttachmentById": "通过 ID 添加附件", |  | ||||||
|   "enterFileId": "输入文件 ID", |  | ||||||
|   "fileIdCannotBeEmpty": "文件 ID 不能为空", |  | ||||||
|   "failedToFetchFile": "获取文件失败: {}", |  | ||||||
|     "createDirectMessage": "创建新私人消息", |     "createDirectMessage": "创建新私人消息", | ||||||
|     "gotoDirectMessage": "前往私信", |     "gotoDirectMessage": "前往私信", | ||||||
|     "react": "反应", |     "react": "反应", | ||||||
| @@ -350,7 +345,7 @@ | |||||||
|     "accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。", |     "accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。", | ||||||
|     "unauthorized": "未授权", |     "unauthorized": "未授权", | ||||||
|     "unauthorizedHint": "您未登录或会话已过期,请重新登录。", |     "unauthorizedHint": "您未登录或会话已过期,请重新登录。", | ||||||
|   "publisherBelongsTo": "属于 {}", |     "publisherBelongsTo": "属于", | ||||||
|     "postContent": "内容", |     "postContent": "内容", | ||||||
|     "postSettings": "设置", |     "postSettings": "设置", | ||||||
|     "postPublisherUnselected": "未指定发布者", |     "postPublisherUnselected": "未指定发布者", | ||||||
| @@ -495,20 +490,29 @@ | |||||||
|     "paymentError": "付款失败: {error}", |     "paymentError": "付款失败: {error}", | ||||||
|     "usePinInstead": "使用 PIN 码", |     "usePinInstead": "使用 PIN 码", | ||||||
|     "levelProgress": "等级进度", |     "levelProgress": "等级进度", | ||||||
|  |     "unlockedFeatures": "已解锁的功能", | ||||||
|  |     "unlockedFeaturesDescription": "在您当前级别上解锁的功能将显示在这里。", | ||||||
|     "stellarMembership": "恒星计划", |     "stellarMembership": "恒星计划", | ||||||
|     "upgradeYourPlan": "升级您的计划", |     "upgradeYourPlan": "升级您的计划", | ||||||
|     "chooseYourPlan": "选择你的方案", |     "chooseYourPlan": "选择你的方案", | ||||||
|     "currentMembership": "当前:{}", |     "currentMembership": "当前:{}", | ||||||
|   "currentMembershipMember": "恒星计划「{}」级会员", |  | ||||||
|     "membershipExpires": "过期于:{}", |     "membershipExpires": "过期于:{}", | ||||||
|     "membershipTierStellar": "恒星", |     "membershipTierStellar": "恒星", | ||||||
|     "membershipTierNova": "新星", |     "membershipTierNova": "新星", | ||||||
|     "membershipTierSupernova": "超新星", |     "membershipTierSupernova": "超新星", | ||||||
|     "membershipTierUnknown": "未知", |     "membershipTierUnknown": "未知", | ||||||
|   "membershipPriceStellar": "每月 1200 源点,至少需要 3 级", |     "membershipPriceStellar": "每月 10 金点", | ||||||
|   "membershipPriceNova": "每月 2400 源点,至少需要 6 级", |     "membershipPriceNova": "每月 20 金点", | ||||||
|   "membershipPriceSupernova": "每月 3600 源点,至少需要 9 级", |     "membershipPriceSupernova": "每月 30 金点", | ||||||
|     "membershipFeatureBasic": "基础功能", |     "membershipFeatureBasic": "基础功能", | ||||||
|  |     "membershipFeaturePrioritySupport": "优先支持", | ||||||
|  |     "membershipFeatureAdFree": "无广告", | ||||||
|  |     "membershipFeatureAllPrimary": "所有主要功能", | ||||||
|  |     "membershipFeatureAdvancedCustomization": "高级自定义", | ||||||
|  |     "membershipFeatureEarlyAccess": "抢先体验", | ||||||
|  |     "membershipFeatureAllNova": "所有「新星」功能", | ||||||
|  |     "membershipFeatureExclusiveContent": "限定内容", | ||||||
|  |     "membershipFeatureVipSupport": "VIP 支持", | ||||||
|     "membershipCurrentBadge": "当前", |     "membershipCurrentBadge": "当前", | ||||||
|     "restorePurchase": "恢复购买", |     "restorePurchase": "恢复购买", | ||||||
|     "restorePurchaseDescription": "输入您付款的提供商和订单 ID 以恢复您的购买。", |     "restorePurchaseDescription": "输入您付款的提供商和订单 ID 以恢复您的购买。", | ||||||
| @@ -518,32 +522,161 @@ | |||||||
|     "enterOrderId": "输入您的订单 ID", |     "enterOrderId": "输入您的订单 ID", | ||||||
|     "restore": "恢复", |     "restore": "恢复", | ||||||
|     "keyboardShortcuts": "键盘快捷键", |     "keyboardShortcuts": "键盘快捷键", | ||||||
|   "about": "关于", |     "safetyReport": "举报", | ||||||
|   "membershipCancel": "取消会员订阅", |     "safetyReportTitle": "举报", | ||||||
|   "membershipCancelConfirm": "您确定要取消您的会员订阅?", |     "safetyReportDescription": "通过举报不合适的内容和行为来维护我们社区的稳定。", | ||||||
|   "membershipCancelHint": "您确定要取消您的会员订阅吗?您将不会再被收费。您的会员资格将在当前计费周期结束前保持有效。并且您在当前订阅结束之前无法重新订阅。", |     "safetyReportType": "举报类型", | ||||||
|   "membershipCancelSuccess": "您的会员订阅已成功取消。", |     "safetyReportReason": "更多证据", | ||||||
|   "aboutScreenTitle": "关于", |     "safetyReportReasonHint": "请提供更多证据……", | ||||||
|   "aboutScreenVersionInfo": "版本 {} ({})", |     "safetyReportSubmit": "提交举报", | ||||||
|   "aboutScreenAppInfoSectionTitle": "应用信息", |     "safetyReportSubmitting": "提交中……", | ||||||
|   "aboutScreenPackageNameLabel": "包名", |     "safetyReportSuccess": "举报成功,感谢您参与维护社区健康发展。", | ||||||
|   "aboutScreenVersionLabel": "版本", |     "safetyReportError": "举报失败,请稍后重试。", | ||||||
|   "aboutScreenBuildNumberLabel": "构建编号", |     "safetyReportReasonRequired": "请提供举报证据", | ||||||
|   "aboutScreenLinksSectionTitle": "链接", |     "safetyReportTypeSpam": "垃圾或导向错误", | ||||||
|   "aboutScreenPrivacyPolicyTitle": "隐私政策", |     "safetyReportTypeHarassment": "骚扰或暴力行为", | ||||||
|   "aboutScreenTermsOfServiceTitle": "服务条款", |     "safetyReportTypeHateSpeech": "歧视言论", | ||||||
|   "aboutScreenOpenSourceLicensesTitle": "开源许可证", |     "safetyReportTypeViolence": "威胁或暴力内容", | ||||||
|   "aboutScreenDeveloperSectionTitle": "开发者", |     "safetyReportTypeAdultContent": "成人内容", | ||||||
|   "aboutScreenContactUsTitle": "联系我们", |     "safetyReportTypeIntellectualProperty": "抄袭", | ||||||
|   "aboutScreenLicenseTitle": "许可证", |     "safetyReportTypeOther": "其它", | ||||||
|   "aboutScreenLicenseContent": "GNU Affero General Public License v3.0", |     "safetyReportTypeInappropriate": "不良内容", | ||||||
|   "aboutScreenCopyright": "版权所有 © 索尔辛茨 {}", |     "safetyReportTypeCopyright": "版权侵害", | ||||||
|   "aboutScreenMadeWith": "由 Solar Network Team 用 ❤︎️ 制作", |     "safetyReportSuccessTitle": "举报成功", | ||||||
|   "aboutScreenFailedToLoadPackageInfo": "加载包信息失败:{error}", |     "safetyReportErrorTitle": "错误", | ||||||
|   "copiedToClipboard": "已复制到剪贴板", |     "discover": "发现", | ||||||
|   "copyToClipboardTooltip": "复制到剪贴板", |     "joinRealm": "加入领域", | ||||||
|   "postForwardingTo": "转发给", |     "removePublisherMember": "移除发布者", | ||||||
|   "postReplyingTo": "回复给", |     "removePublisherMemberHint": "你确定要将这个成员从发布者中移除?", | ||||||
|   "postEditing": "您正在编辑现有帖子", |     "drafts": "草稿箱", | ||||||
|   "postArticle": "文章" |     "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:shared_preferences/shared_preferences.dart'; | ||||||
| import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | ||||||
| import 'package:flutter_native_splash/flutter_native_splash.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:url_launcher/url_launcher_string.dart'; | ||||||
| import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; | import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; | ||||||
|  |  | ||||||
| @@ -244,7 +245,8 @@ class IslandApp extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final router = ref.watch(routerProvider); |     final router = ref.watch(routerProvider); | ||||||
|  |  | ||||||
|     return MaterialApp.router( |     return KeyboardNavigation( | ||||||
|  |       child: MaterialApp.router( | ||||||
|         theme: theme?.light, |         theme: theme?.light, | ||||||
|         darkTheme: theme?.dark, |         darkTheme: theme?.dark, | ||||||
|         themeMode: ThemeMode.system, |         themeMode: ThemeMode.system, | ||||||
| @@ -268,6 +270,7 @@ class IslandApp extends HookConsumerWidget { | |||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -216,6 +216,7 @@ class RelationshipScreen extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => AccountPickerSheet(), |         builder: (context) => AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
| @@ -227,6 +227,7 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
| @@ -589,6 +589,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
| @@ -727,7 +728,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|                                       apiClientProvider, |                                       apiClientProvider, | ||||||
|                                     ); |                                     ); | ||||||
|                                     await apiClient.delete( |                                     await apiClient.delete( | ||||||
|                                       '/chat/$roomId/members/${member.accountId}', |                                       '/sphere/chat/$roomId/members/${member.accountId}', | ||||||
|                                     ); |                                     ); | ||||||
|                                     // Refresh both providers |                                     // Refresh both providers | ||||||
|                                     memberNotifier.reset(); |                                     memberNotifier.reset(); | ||||||
|   | |||||||
| @@ -708,6 +708,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|  |         useRootNavigator: true, | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|         context: context, |         context: context, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|   | |||||||
| @@ -180,6 +180,7 @@ class StickerPackDetailScreen extends HookConsumerWidget { | |||||||
|                                               .pushNamed( |                                               .pushNamed( | ||||||
|                                                 'creatorStickerEdit', |                                                 'creatorStickerEdit', | ||||||
|                                                 pathParameters: { |                                                 pathParameters: { | ||||||
|  |                                                   'name': pubName, | ||||||
|                                                   'packId': id, |                                                   'packId': id, | ||||||
|                                                   'id': sticker.id, |                                                   'id': sticker.id, | ||||||
|                                                 }, |                                                 }, | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget { | |||||||
|               context |               context | ||||||
|                   .pushNamed( |                   .pushNamed( | ||||||
|                     'creatorStickerPackNew', |                     'creatorStickerPackNew', | ||||||
|                     queryParameters: {'name': pubName}, |                     pathParameters: {'name': pubName}, | ||||||
|                   ) |                   ) | ||||||
|                   .then((value) { |                   .then((value) { | ||||||
|                     if (value != null) { |                     if (value != null) { | ||||||
| @@ -187,10 +187,8 @@ class EditStickerPacksScreen extends HookConsumerWidget { | |||||||
|             'description': descriptionController.text, |             'description': descriptionController.text, | ||||||
|             'prefix': prefixController.text, |             'prefix': prefixController.text, | ||||||
|           }, |           }, | ||||||
|           options: Options( |           queryParameters: {'pub': pubName}, | ||||||
|             method: packId == null ? 'POST' : 'PATCH', |           options: Options(method: packId == null ? 'POST' : 'PATCH'), | ||||||
|             headers: {'X-Pub': pubName}, |  | ||||||
|           ), |  | ||||||
|         ); |         ); | ||||||
|         if (!context.mounted) return; |         if (!context.mounted) return; | ||||||
|         context.pop(SnStickerPack.fromJson(resp.data)); |         context.pop(SnStickerPack.fromJson(resp.data)); | ||||||
|   | |||||||
| @@ -488,6 +488,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|  |         useRootNavigator: true, | ||||||
|         context: context, |         context: context, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -67,6 +67,9 @@ Future<void> subscribePushNotification( | |||||||
|   Dio apiClient, { |   Dio apiClient, { | ||||||
|   bool detailedErrors = false, |   bool detailedErrors = false, | ||||||
| }) async { | }) async { | ||||||
|  |   if (Platform.isLinux){ | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|   await FirebaseMessaging.instance.requestPermission( |   await FirebaseMessaging.instance.requestPermission( | ||||||
|     alert: true, |     alert: true, | ||||||
|     badge: true, |     badge: true, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  |  | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -44,9 +45,8 @@ class AccountPickerSheet extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Container( |     return Container( | ||||||
|       constraints: BoxConstraints( |       padding: MediaQuery.of(context).viewInsets, | ||||||
|         maxHeight: MediaQuery.of(context).size.height * 0.4, |       height: MediaQuery.of(context).size.height * 0.6, | ||||||
|       ), |  | ||||||
|       child: Column( |       child: Column( | ||||||
|         children: [ |         children: [ | ||||||
|           Padding( |           Padding( | ||||||
| @@ -54,8 +54,8 @@ class AccountPickerSheet extends HookConsumerWidget { | |||||||
|             child: TextField( |             child: TextField( | ||||||
|               controller: searchController, |               controller: searchController, | ||||||
|               onChanged: onSearchChanged, |               onChanged: onSearchChanged, | ||||||
|               decoration: const InputDecoration( |               decoration: InputDecoration( | ||||||
|                 hintText: 'Search accounts...', |                 hintText: 'searchAccounts'.tr(), | ||||||
|                 contentPadding: EdgeInsets.symmetric( |                 contentPadding: EdgeInsets.symmetric( | ||||||
|                   horizontal: 18, |                   horizontal: 18, | ||||||
|                   vertical: 16, |                   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