Compare commits
	
		
			14 Commits
		
	
	
		
			3.1.0+122
			...
			8236d31ecc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8236d31ecc | |||
| 459a7dade0 | |||
| e6000a660a | |||
| 75abaac205 | |||
| 603d5c3f73 | |||
| 4e4bd99598 | |||
| d1fbe5f15e | |||
| c061ef2132 | |||
| c378309bdd | |||
| b2c5d64fc5 | |||
|  | 5371637b16 | ||
| c5cbf0af37 | |||
| 1a31e22450 | |||
|  | 49db54529d | 
| @@ -5,6 +5,7 @@ plugins { | ||||
|     id("com.android.application") | ||||
|     // START: FlutterFire Configuration | ||||
|     id("com.google.gms.google-services") | ||||
|     id("com.google.firebase.crashlytics") | ||||
|     // END: FlutterFire Configuration | ||||
|     id("kotlin-android") | ||||
|     // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. | ||||
| @@ -51,6 +52,12 @@ android { | ||||
|     buildTypes { | ||||
|         release { | ||||
|             signingConfig = signingConfigs.getByName("release") | ||||
|  | ||||
|             isMinifyEnabled = true | ||||
|             proguardFiles( | ||||
|                 getDefaultProguardFile("proguard-android-optimize.txt"), | ||||
|                 "proguard-rules.pro" | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -58,7 +65,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,12 @@ 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 | ||||
|     id("com.google.firebase.crashlytics") version("2.8.1") 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") | ||||
|   | ||||
| @@ -573,6 +573,7 @@ | ||||
|   "keyboardShortcuts": "Keyboard Shortcuts", | ||||
|   "share": "Share", | ||||
|   "sharePost": "Share Post", | ||||
|   "sharePostPhoto": "Share Post as Photo", | ||||
|   "quickActions": "Quick Actions", | ||||
|   "post": "Post", | ||||
|   "copy": "Copy", | ||||
| @@ -789,5 +790,8 @@ | ||||
|   "linkKey": "Link Name", | ||||
|   "linkValue": "URL", | ||||
|   "debugOptions": "Debug Options", | ||||
|   "joinedAt": "Joined at {}" | ||||
|   "joinedAt": "Joined at {}", | ||||
|   "searchAccounts": "Search accounts...", | ||||
|   "webFeeds": "Web Feeds", | ||||
|   "polls": "Polls" | ||||
| } | ||||
|   | ||||
| @@ -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": "未指定发布者", | ||||
| @@ -478,7 +473,7 @@ | ||||
|   "description": "描述", | ||||
|   "pinCode": "PIN 码", | ||||
|   "biometric": "生物识别", | ||||
|   "enterPinToConfirm": "请输入您的 6 位数字 PIN 以确认付款", | ||||
|   "enterPinToConfirm": "请输入您的6位数字 PIN 以确认付款", | ||||
|   "clearPin": "清除 PIN 码", | ||||
|   "useBiometricToConfirm": "使用生物特征认证来确认付款", | ||||
|   "touchSensorToAuthenticate": "触摸传感器进行身份验证", | ||||
| @@ -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,11 +522,168 @@ | ||||
|   "enterOrderId": "输入您的订单 ID", | ||||
|   "restore": "恢复", | ||||
|   "keyboardShortcuts": "键盘快捷键", | ||||
|   "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": "分享帖子", | ||||
|   "sharePostAsPhoto": "通过图片分享帖子", | ||||
|   "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": "关于", | ||||
|   "membershipCancel": "取消会员订阅", | ||||
|   "membershipCancelConfirm": "您确定要取消您的会员订阅?", | ||||
|   "membershipCancelHint": "您确定要取消您的会员订阅吗?您将不会再被收费。您的会员资格将在当前计费周期结束前保持有效。并且您在当前订阅结束之前无法重新订阅。", | ||||
|   "membershipCancelSuccess": "您的会员订阅已成功取消。", | ||||
|   "membershipCancel": "取消会员资格", | ||||
|   "membershipCancelConfirm": "你确定要取消会员资格吗?", | ||||
|   "membershipCancelHint": "你确定要取消会员资格吗?你将不会再次被扣费。你的会员资格将在当前计费周期结束前保持有效。并且你将无法重新订阅,直到当前订阅结束。", | ||||
|   "membershipCancelSuccess": "你的会员资格已成功取消。", | ||||
|   "aboutScreenTitle": "关于", | ||||
|   "aboutScreenVersionInfo": "版本 {} ({})", | ||||
|   "aboutScreenAppInfoSectionTitle": "应用信息", | ||||
| @@ -532,18 +693,103 @@ | ||||
|   "aboutScreenLinksSectionTitle": "链接", | ||||
|   "aboutScreenPrivacyPolicyTitle": "隐私政策", | ||||
|   "aboutScreenTermsOfServiceTitle": "服务条款", | ||||
|   "aboutScreenOpenSourceLicensesTitle": "开源许可证", | ||||
|   "aboutScreenOpenSourceLicensesTitle": "开源许可", | ||||
|   "aboutScreenDeveloperSectionTitle": "开发者", | ||||
|   "aboutScreenContactUsTitle": "联系我们", | ||||
|   "aboutScreenLicenseTitle": "许可证", | ||||
|   "aboutScreenLicenseTitle": "许可", | ||||
|   "aboutScreenLicenseContent": "GNU Affero General Public License v3.0", | ||||
|   "aboutScreenCopyright": "版权所有 © 索尔辛茨 {}", | ||||
|   "aboutScreenMadeWith": "由 Solar Network Team 用 ❤︎️ 制作", | ||||
|   "aboutScreenFailedToLoadPackageInfo": "加载包信息失败:{error}", | ||||
|   "aboutScreenCopyright": "版权所有 © Solsynth {}", | ||||
|   "aboutScreenMadeWith": "由 Solar Network 团队用 ❤︎️ 制作", | ||||
|   "aboutScreenFailedToLoadPackageInfo": "无法加载包信息:{error}", | ||||
|   "copiedToClipboard": "已复制到剪贴板", | ||||
|   "copyToClipboardTooltip": "复制到剪贴板", | ||||
|   "postForwardingTo": "转发给", | ||||
|   "postReplyingTo": "回复给", | ||||
|   "postEditing": "您正在编辑现有帖子", | ||||
|   "postArticle": "文章" | ||||
|   "postForwardingTo": "正在转发到", | ||||
|   "postReplyingTo": "正在回复", | ||||
|   "postReplyPlaceholder": "发表你的回复", | ||||
|   "postEditing": "你正在编辑一个现有的帖子", | ||||
|   "postArticle": "文章", | ||||
|   "aboutDeviceName": "设备名称", | ||||
|   "aboutDeviceIdentifier": "设备标识符", | ||||
|   "donate": "捐赠", | ||||
|   "donateDescription": "支持我们继续开发 Solar Network,并保持服务器运行。", | ||||
|   "fileId": "文件ID", | ||||
|   "fileIdHint": "文件ID是你通过 Solar Network Drive 上传文件后获得的ID。", | ||||
|   "translate": "翻译", | ||||
|   "translating": "正在翻译", | ||||
|   "translated": "已翻译", | ||||
|   "reactionThumbUp": "点赞", | ||||
|   "reactionThumbDown": "踩", | ||||
|   "reactionJustOkay": "还行", | ||||
|   "reactionCry": "哭", | ||||
|   "reactionConfuse": "困惑", | ||||
|   "reactionClap": "鼓掌", | ||||
|   "reactionLaugh": "笑", | ||||
|   "reactionAngry": "生气", | ||||
|   "reactionParty": "派对", | ||||
|   "reactionPray": "祈祷", | ||||
|   "reactionHeart": "心", | ||||
|   "selectMicrophone": "选择麦克风", | ||||
|   "selectCamera": "选择摄像头", | ||||
|   "switchedTo": "已切换到 {}", | ||||
|   "connecting": "正在连接", | ||||
|   "reconnecting": "正在重新连接", | ||||
|   "disconnected": "已断开连接", | ||||
|   "connected": "已连接", | ||||
|   "repliesLoadMore": "加载更多回复", | ||||
|   "attachmentsRecentUploads": "最近上传", | ||||
|   "attachmentsManualInput": "手动输入", | ||||
|   "crop": "裁剪", | ||||
|   "rename": "重命名", | ||||
|   "markAsSensitive": "标记为敏感", | ||||
|   "fileName": "文件名", | ||||
|   "sensitiveCategories.language": "语言", | ||||
|   "sensitiveCategories.sexualContent": "色情内容", | ||||
|   "sensitiveCategories.violence": "暴力", | ||||
|   "sensitiveCategories.profanity": "亵渎", | ||||
|   "sensitiveCategories.hateSpeech": "仇恨言论", | ||||
|   "sensitiveCategories.racism": "种族主义", | ||||
|   "sensitiveCategories.adultContent": "成人内容", | ||||
|   "sensitiveCategories.drugAbuse": "药物滥用", | ||||
|   "sensitiveCategories.alcoholAbuse": "酗酒", | ||||
|   "sensitiveCategories.gambling": "赌博", | ||||
|   "sensitiveCategories.selfHarm": "自残", | ||||
|   "sensitiveCategories.childAbuse": "虐待儿童", | ||||
|   "sensitiveCategories.other": "其他", | ||||
|   "poll": "投票", | ||||
|   "pollsRecent": "最近投票", | ||||
|   "pollCreateNew": "创建新投票", | ||||
|   "pollCreateNewHint": "为你的帖子创建一个新投票。选择一个发布者然后继续。", | ||||
|   "publisher": "发布者", | ||||
|   "publisherHint": "输入发布者名称", | ||||
|   "publisherCannotBeEmpty": "发布者不能为空", | ||||
|   "operationFailed": "操作失败:{}", | ||||
|   "stickerMarketplace": "贴纸市场", | ||||
|   "stickerPackAdded": "贴纸包已添加到你的收藏", | ||||
|   "stickerPackRemoved": "贴纸包已从你的收藏中移除", | ||||
|   "addPack": "添加贴纸包", | ||||
|   "removePack": "移除贴纸包", | ||||
|   "browseAndAddStickers": "浏览并添加贴纸包", | ||||
|   "stickerPack": "贴纸包", | ||||
|   "postCategoryTechnology": "科技", | ||||
|   "postCategoryTravel": "旅行", | ||||
|   "postCategoryFood": "美食", | ||||
|   "postCategoryHealth": "健康", | ||||
|   "postCategoryScience": "科学", | ||||
|   "postCategorySports": "体育", | ||||
|   "postCategoryFinance": "金融", | ||||
|   "postCategoryLife": "生活", | ||||
|   "postCategoryArt": "艺术", | ||||
|   "postCategoryStudy": "学习", | ||||
|   "postCategoryGaming": "游戏", | ||||
|   "postCategoryProgramming": "编程", | ||||
|   "postCategoryMusic": "音乐", | ||||
|   "links": "链接", | ||||
|   "addLink": "添加链接", | ||||
|   "linkKey": "链接名称", | ||||
|   "linkValue": "URL", | ||||
|   "debugOptions": "调试选项", | ||||
|   "joinedAt": "加入于 {}", | ||||
|   "searchAccounts": "搜索帐号……", | ||||
|   "webFeeds": "订阅源", | ||||
|   "polls": "投票" | ||||
| } | ||||
|   | ||||
							
								
								
									
										105
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							| @@ -42,22 +42,62 @@ PODS: | ||||
|     - Flutter | ||||
|   - Firebase/CoreOnly (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|   - Firebase/Crashlytics (12.0.0): | ||||
|     - Firebase/CoreOnly | ||||
|     - FirebaseCrashlytics (~> 12.0.0) | ||||
|   - Firebase/Messaging (12.0.0): | ||||
|     - Firebase/CoreOnly | ||||
|     - FirebaseMessaging (~> 12.0.0) | ||||
|   - firebase_analytics (12.0.0): | ||||
|     - firebase_core | ||||
|     - FirebaseAnalytics (= 12.0.0) | ||||
|     - Flutter | ||||
|   - firebase_core (4.0.0): | ||||
|     - Firebase/CoreOnly (= 12.0.0) | ||||
|     - Flutter | ||||
|   - firebase_crashlytics (5.0.0): | ||||
|     - Firebase/Crashlytics (= 12.0.0) | ||||
|     - firebase_core | ||||
|     - Flutter | ||||
|   - firebase_messaging (16.0.0): | ||||
|     - Firebase/Messaging (= 12.0.0) | ||||
|     - firebase_core | ||||
|     - Flutter | ||||
|   - FirebaseAnalytics (12.0.0): | ||||
|     - FirebaseAnalytics/Default (= 12.0.0) | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - FirebaseAnalytics/Default (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - GoogleAppMeasurement/Default (= 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - FirebaseCore (12.0.0): | ||||
|     - FirebaseCoreInternal (~> 12.0.0) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - GoogleUtilities/Logger (~> 8.1) | ||||
|   - FirebaseCoreExtension (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|   - FirebaseCoreInternal (12.0.0): | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|   - FirebaseCrashlytics (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - FirebaseRemoteConfigInterop (~> 12.0.0) | ||||
|     - FirebaseSessions (~> 12.0.0) | ||||
|     - GoogleDataTransport (~> 10.1) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesObjC (~> 2.4) | ||||
|   - FirebaseInstallations (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
| @@ -72,6 +112,16 @@ PODS: | ||||
|     - GoogleUtilities/Reachability (~> 8.1) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - FirebaseRemoteConfigInterop (12.0.0) | ||||
|   - FirebaseSessions (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseCoreExtension (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - GoogleDataTransport (~> 10.1) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesSwift (~> 2.1) | ||||
|   - Flutter (1.0.0) | ||||
|   - flutter_app_update (0.0.1): | ||||
|     - Flutter | ||||
| @@ -101,6 +151,32 @@ PODS: | ||||
|   - gal (1.0.0): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - GoogleAdsOnDeviceConversion (2.1.0): | ||||
|     - GoogleUtilities/Logger (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - GoogleAppMeasurement/Core (12.0.0): | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - GoogleAppMeasurement/Default (12.0.0): | ||||
|     - GoogleAdsOnDeviceConversion (= 2.1.0) | ||||
|     - GoogleAppMeasurement/Core (= 12.0.0) | ||||
|     - GoogleAppMeasurement/IdentitySupport (= 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - GoogleAppMeasurement/IdentitySupport (12.0.0): | ||||
|     - GoogleAppMeasurement/Core (= 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - GoogleDataTransport (10.1.0): | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesObjC (~> 2.4) | ||||
| @@ -114,6 +190,9 @@ PODS: | ||||
|   - GoogleUtilities/Logger (8.1.0): | ||||
|     - GoogleUtilities/Environment | ||||
|     - GoogleUtilities/Privacy | ||||
|   - GoogleUtilities/MethodSwizzler (8.1.0): | ||||
|     - GoogleUtilities/Logger | ||||
|     - GoogleUtilities/Privacy | ||||
|   - GoogleUtilities/Network (8.1.0): | ||||
|     - GoogleUtilities/Logger | ||||
|     - "GoogleUtilities/NSData+zlib" | ||||
| @@ -162,6 +241,8 @@ PODS: | ||||
|   - pointer_interceptor_ios (0.0.1): | ||||
|     - Flutter | ||||
|   - PromisesObjC (2.4.0) | ||||
|   - PromisesSwift (2.4.0): | ||||
|     - PromisesObjC (= 2.4.0) | ||||
|   - receive_sharing_intent (1.8.1): | ||||
|     - Flutter | ||||
|   - record_ios (1.0.0): | ||||
| @@ -222,7 +303,9 @@ DEPENDENCIES: | ||||
|   - croppy (from `.symlinks/plugins/croppy/ios`) | ||||
|   - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) | ||||
|   - file_picker (from `.symlinks/plugins/file_picker/ios`) | ||||
|   - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) | ||||
|   - firebase_core (from `.symlinks/plugins/firebase_core/ios`) | ||||
|   - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) | ||||
|   - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) | ||||
|   - Flutter (from `Flutter`) | ||||
|   - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) | ||||
| @@ -265,16 +348,24 @@ SPEC REPOS: | ||||
|     - DKImagePickerController | ||||
|     - DKPhotoGallery | ||||
|     - Firebase | ||||
|     - FirebaseAnalytics | ||||
|     - FirebaseCore | ||||
|     - FirebaseCoreExtension | ||||
|     - FirebaseCoreInternal | ||||
|     - FirebaseCrashlytics | ||||
|     - FirebaseInstallations | ||||
|     - FirebaseMessaging | ||||
|     - FirebaseRemoteConfigInterop | ||||
|     - FirebaseSessions | ||||
|     - GoogleAdsOnDeviceConversion | ||||
|     - GoogleAppMeasurement | ||||
|     - GoogleDataTransport | ||||
|     - GoogleUtilities | ||||
|     - Kingfisher | ||||
|     - nanopb | ||||
|     - OrderedSet | ||||
|     - PromisesObjC | ||||
|     - PromisesSwift | ||||
|     - SAMKeychain | ||||
|     - SDWebImage | ||||
|     - sqlite3 | ||||
| @@ -290,8 +381,12 @@ EXTERNAL SOURCES: | ||||
|     :path: ".symlinks/plugins/device_info_plus/ios" | ||||
|   file_picker: | ||||
|     :path: ".symlinks/plugins/file_picker/ios" | ||||
|   firebase_analytics: | ||||
|     :path: ".symlinks/plugins/firebase_analytics/ios" | ||||
|   firebase_core: | ||||
|     :path: ".symlinks/plugins/firebase_core/ios" | ||||
|   firebase_crashlytics: | ||||
|     :path: ".symlinks/plugins/firebase_crashlytics/ios" | ||||
|   firebase_messaging: | ||||
|     :path: ".symlinks/plugins/firebase_messaging/ios" | ||||
|   Flutter: | ||||
| @@ -370,12 +465,19 @@ SPEC CHECKSUMS: | ||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||
|   file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be | ||||
|   Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 | ||||
|   firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d | ||||
|   firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4 | ||||
|   firebase_crashlytics: 2c6c1a17900a38081d938330e9f48e60ec5b255d | ||||
|   firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361 | ||||
|   FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7 | ||||
|   FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a | ||||
|   FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361 | ||||
|   FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55 | ||||
|   FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7 | ||||
|   FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988 | ||||
|   FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde | ||||
|   FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613 | ||||
|   FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42 | ||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 | ||||
|   flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9 | ||||
|   flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 | ||||
| @@ -387,6 +489,8 @@ SPEC CHECKSUMS: | ||||
|   flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 | ||||
|   flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457 | ||||
|   gal: baecd024ebfd13c441269ca7404792a7152fde89 | ||||
|   GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 | ||||
|   GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3 | ||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||
|   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 | ||||
|   image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a | ||||
| @@ -404,6 +508,7 @@ SPEC CHECKSUMS: | ||||
|   path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 | ||||
|   pointer_interceptor_ios: ec847ef8b0915778bed2b2cef636f4d177fa8eed | ||||
|   PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 | ||||
|   PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 | ||||
|   receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 | ||||
|   record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b | ||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||
|   | ||||
| @@ -439,6 +439,7 @@ | ||||
| 				3B06AD1E1E4923F5004D2608 /* Thin Binary */, | ||||
| 				8C0351B03869BBF493808288 /* [CP] Embed Pods Frameworks */, | ||||
| 				5E7D6EF29B671AC7EDBA5649 /* [CP] Copy Pods Resources */, | ||||
| 				E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */, | ||||
| 			); | ||||
| 			buildRules = ( | ||||
| 			); | ||||
| @@ -682,6 +683,24 @@ | ||||
| 			shellPath = /bin/sh; | ||||
| 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; | ||||
| 		}; | ||||
| 		E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			inputFileListPaths = ( | ||||
| 			); | ||||
| 			inputPaths = ( | ||||
| 			); | ||||
| 			name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\""; | ||||
| 			outputFileListPaths = ( | ||||
| 			); | ||||
| 			outputPaths = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 			shellPath = /bin/sh; | ||||
| 			shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n  # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n  DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n  PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n  PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n"; | ||||
| 		}; | ||||
| 		E947029FCA058878F9B63890 /* [CP] Check Pods Manifest.lock */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
|   | ||||
| @@ -61,10 +61,8 @@ class DefaultFirebaseOptions { | ||||
|     messagingSenderId: '961776991058', | ||||
|     projectId: 'solian-0x001', | ||||
|     storageBucket: 'solian-0x001.firebasestorage.app', | ||||
|     androidClientId: | ||||
|         '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||
|     iosClientId: | ||||
|         '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||
|     androidClientId: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||
|     iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||
|     iosBundleId: 'dev.solsynth.solian', | ||||
|   ); | ||||
|  | ||||
| @@ -74,10 +72,8 @@ class DefaultFirebaseOptions { | ||||
|     messagingSenderId: '961776991058', | ||||
|     projectId: 'solian-0x001', | ||||
|     storageBucket: 'solian-0x001.firebasestorage.app', | ||||
|     androidClientId: | ||||
|         '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||
|     iosClientId: | ||||
|         '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||
|     androidClientId: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||
|     iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||
|     iosBundleId: 'dev.solsynth.solian', | ||||
|   ); | ||||
|  | ||||
| @@ -90,4 +86,5 @@ class DefaultFirebaseOptions { | ||||
|     storageBucket: 'solian-0x001.firebasestorage.app', | ||||
|     measurementId: 'G-JD1YEG9D6F', | ||||
|   ); | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -4,6 +4,7 @@ import 'dart:io'; | ||||
| import 'package:croppy/croppy.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart' hide TextDirection; | ||||
| import 'package:firebase_core/firebase_core.dart'; | ||||
| import 'package:firebase_crashlytics/firebase_crashlytics.dart'; | ||||
| import 'package:firebase_messaging/firebase_messaging.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -61,6 +62,12 @@ void main() async { | ||||
|       FirebaseMessaging.onBackgroundMessage( | ||||
|         _firebaseMessagingBackgroundHandler, | ||||
|       ); | ||||
|       FlutterError.onError = | ||||
|           FirebaseCrashlytics.instance.recordFlutterFatalError; | ||||
|       PlatformDispatcher.instance.onError = (error, stack) { | ||||
|         FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); | ||||
|         return true; | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     log("[SplashScreen] Firebase is ready!"); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:firebase_analytics/firebase_analytics.dart'; | ||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/user.dart'; | ||||
| @@ -17,6 +18,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | ||||
|       final response = await client.get('/id/accounts/me'); | ||||
|       final user = SnAccount.fromJson(response.data); | ||||
|       state = AsyncValue.data(user); | ||||
|       FirebaseAnalytics.instance.setUserId(id: user.id); | ||||
|     } catch (error, stackTrace) { | ||||
|       log( | ||||
|         "[UserInfo] Failed to fetch user info...", | ||||
| @@ -33,6 +35,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | ||||
|     final prefs = _ref.read(sharedPreferencesProvider); | ||||
|     await prefs.remove(kTokenPairStoreKey); | ||||
|     _ref.invalidate(tokenProvider); | ||||
|     FirebaseAnalytics.instance.setUserId(id: null); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import 'package:firebase_analytics/firebase_analytics.dart'; | ||||
| import 'package:firebase_analytics/observer.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -59,6 +61,9 @@ final routerProvider = Provider<GoRouter>((ref) { | ||||
|   return GoRouter( | ||||
|     navigatorKey: rootNavigatorKey, | ||||
|     initialLocation: '/', | ||||
|     observers: [ | ||||
|       FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance), | ||||
|     ], | ||||
|     routes: [ | ||||
|       ShellRoute( | ||||
|         navigatorKey: _shellNavigatorKey, | ||||
|   | ||||
| @@ -236,7 +236,7 @@ class AccountScreen extends HookConsumerWidget { | ||||
|             ), | ||||
|             ListTile( | ||||
|               minTileHeight: 48, | ||||
|               title: Text('abuseReports').tr(), | ||||
|               title: Text('abuseReport').tr(), | ||||
|               contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|               leading: const Icon(Symbols.gavel), | ||||
|               trailing: const Icon(Symbols.chevron_right), | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -382,7 +382,7 @@ class CreatorHubScreen extends HookConsumerWidget { | ||||
|                           ), | ||||
|                           ListTile( | ||||
|                             minTileHeight: 48, | ||||
|                             title: const Text('Polls'), | ||||
|                             title: Text('polls').tr(), | ||||
|                             trailing: const Icon(Symbols.chevron_right), | ||||
|                             leading: const Icon(Symbols.poll), | ||||
|                             contentPadding: const EdgeInsets.symmetric( | ||||
| @@ -419,7 +419,7 @@ class CreatorHubScreen extends HookConsumerWidget { | ||||
|                           ), | ||||
|                           ListTile( | ||||
|                             minTileHeight: 48, | ||||
|                             title: const Text('Web Feeds').tr(), | ||||
|                             title: const Text('webFeeds').tr(), | ||||
|                             trailing: const Icon(Symbols.chevron_right), | ||||
|                             leading: const Icon(Symbols.rss_feed), | ||||
|                             contentPadding: const EdgeInsets.symmetric( | ||||
| @@ -659,7 +659,7 @@ class PublisherMemberNotifier extends StateNotifier<PublisherMemberState> { | ||||
|  | ||||
|     try { | ||||
|       final response = await _apiClient.get( | ||||
|         '/publishers/$publisherUname/members', | ||||
|         '/sphere/publishers/$publisherUname/members', | ||||
|         queryParameters: {'offset': offset, 'take': take}, | ||||
|       ); | ||||
|  | ||||
| @@ -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(), | ||||
| @@ -719,6 +720,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | ||||
|           '/publishers/$publisherUname/invites', | ||||
|           data: {'related_user_id': result.id, 'role': 0}, | ||||
|         ); | ||||
|         // Refresh both providers | ||||
|         memberNotifier.reset(); | ||||
|         await memberNotifier.loadMore(); | ||||
|         ref.invalidate(memberListProvider); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
| @@ -822,6 +826,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | ||||
|                                       ), | ||||
|                                 ).then((value) { | ||||
|                                   if (value != null) { | ||||
|                                     // Refresh both providers | ||||
|                                     memberNotifier.reset(); | ||||
|                                     memberNotifier.loadMore(); | ||||
|                                     ref.invalidate(memberListProvider); | ||||
|                                   } | ||||
|                                 }); | ||||
| @@ -843,6 +850,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | ||||
|                                     await apiClient.delete( | ||||
|                                       '/publishers/$publisherUname/members/${member.accountId}', | ||||
|                                     ); | ||||
|                                     // Refresh both providers | ||||
|                                     memberNotifier.reset(); | ||||
|                                     memberNotifier.loadMore(); | ||||
|                                     ref.invalidate(memberListProvider); | ||||
|                                   } catch (err) { | ||||
|                                     showErrorAlert(err); | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -14,6 +14,7 @@ class PollSubmit extends ConsumerStatefulWidget { | ||||
|     this.initialAnswers, | ||||
|     this.onCancel, | ||||
|     this.showProgress = true, | ||||
|     this.isReadonly = false, | ||||
|   }); | ||||
|  | ||||
|   final SnPollWithStats poll; | ||||
| @@ -31,6 +32,8 @@ class PollSubmit extends ConsumerStatefulWidget { | ||||
|   /// Whether to show a progress indicator (e.g., "2 / N"). | ||||
|   final bool showProgress; | ||||
|  | ||||
|   final bool isReadonly; | ||||
|  | ||||
|   @override | ||||
|   ConsumerState<PollSubmit> createState() => _PollSubmitState(); | ||||
| } | ||||
| @@ -59,7 +62,9 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|     _questions = [...widget.poll.questions] | ||||
|       ..sort((a, b) => a.order.compareTo(b.order)); | ||||
|     _answers = Map<String, dynamic>.from(widget.initialAnswers ?? {}); | ||||
|     _loadCurrentIntoLocalState(); | ||||
|     if (!widget.isReadonly) { | ||||
|       _loadCurrentIntoLocalState(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -74,7 +79,9 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|           [...widget.poll.questions] | ||||
|             ..sort((a, b) => a.order.compareTo(b.order)), | ||||
|         ); | ||||
|       _loadCurrentIntoLocalState(); | ||||
|       if (!widget.isReadonly) { | ||||
|         _loadCurrentIntoLocalState(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -259,10 +266,10 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|                     child: Text( | ||||
|                       widget.poll.description!, | ||||
|                       style: Theme.of(context).textTheme.bodyMedium?.copyWith( | ||||
|                         color: Theme.of( | ||||
|                           context, | ||||
|                         ).textTheme.bodyMedium?.color?.withOpacity(0.7), | ||||
|                       ), | ||||
|                             color: Theme.of( | ||||
|                               context, | ||||
|                             ).textTheme.bodyMedium?.color?.withOpacity(0.7), | ||||
|                           ), | ||||
|                     ), | ||||
|                   ), | ||||
|               ], | ||||
| @@ -287,8 +294,8 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|                 child: Text( | ||||
|                   '*', | ||||
|                   style: Theme.of(context).textTheme.titleMedium?.copyWith( | ||||
|                     color: Theme.of(context).colorScheme.error, | ||||
|                   ), | ||||
|                         color: Theme.of(context).colorScheme.error, | ||||
|                       ), | ||||
|                 ), | ||||
|               ), | ||||
|           ], | ||||
| @@ -299,10 +306,10 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|             child: Text( | ||||
|               q.description!, | ||||
|               style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||
|                 color: Theme.of( | ||||
|                   context, | ||||
|                 ).textTheme.bodySmall?.color?.withOpacity(0.7), | ||||
|               ), | ||||
|                     color: Theme.of( | ||||
|                       context, | ||||
|                     ).textTheme.bodySmall?.color?.withOpacity(0.7), | ||||
|                   ), | ||||
|             ), | ||||
|           ), | ||||
|       ], | ||||
| @@ -340,14 +347,12 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|       case SnPollQuestionType.yesNo: | ||||
|         // yes/no: map {true: count, false: count} | ||||
|         if (raw is Map) { | ||||
|           final int yes = | ||||
|               (raw[true] is int) | ||||
|                   ? raw[true] as int | ||||
|                   : int.tryParse('${raw[true]}') ?? 0; | ||||
|           final int no = | ||||
|               (raw[false] is int) | ||||
|                   ? raw[false] as int | ||||
|                   : int.tryParse('${raw[false]}') ?? 0; | ||||
|           final int yes = (raw[true] is int) | ||||
|               ? raw[true] as int | ||||
|               : int.tryParse('${raw[true]}') ?? 0; | ||||
|           final int no = (raw[false] is int) | ||||
|               ? raw[false] as int | ||||
|               : int.tryParse('${raw[false]}') ?? 0; | ||||
|           final total = (yes + no).clamp(0, 1 << 31); | ||||
|           final yesPct = total == 0 ? 0.0 : yes / total; | ||||
|           final noPct = total == 0 ? 0.0 : no / total; | ||||
| @@ -415,8 +420,8 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|                 Text( | ||||
|                   'Total: $total', | ||||
|                   style: Theme.of(context).textTheme.labelSmall?.copyWith( | ||||
|                     color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||
|                   ), | ||||
|                         color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||
|                       ), | ||||
|                 ), | ||||
|             ], | ||||
|           ); | ||||
| @@ -445,8 +450,8 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|               Text( | ||||
|                 'Stats', | ||||
|                 style: Theme.of(context).textTheme.labelLarge?.copyWith( | ||||
|                   color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||
|                 ), | ||||
|                       color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||
|                     ), | ||||
|               ), | ||||
|               const SizedBox(height: 8), | ||||
|               body, | ||||
| @@ -577,14 +582,13 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|         ), | ||||
|         const Spacer(), | ||||
|         FilledButton.icon( | ||||
|           icon: | ||||
|               _submitting | ||||
|                   ? const SizedBox( | ||||
|                     width: 16, | ||||
|                     height: 16, | ||||
|                     child: CircularProgressIndicator(strokeWidth: 2), | ||||
|                   ) | ||||
|                   : Icon(isLast ? Icons.check : Icons.arrow_forward), | ||||
|           icon: _submitting | ||||
|               ? const SizedBox( | ||||
|                   width: 16, | ||||
|                   height: 16, | ||||
|                   child: CircularProgressIndicator(strokeWidth: 2), | ||||
|                 ) | ||||
|               : Icon(isLast ? Icons.check : Icons.arrow_forward), | ||||
|           label: Text(isLast ? 'Submit' : 'Next'), | ||||
|           onPressed: canProceed ? _next : null, | ||||
|         ), | ||||
| @@ -592,12 +596,92 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildReadonlyView(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         if (widget.poll.title != null || widget.poll.description != null) | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.only(bottom: 12), | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 if (widget.poll.title != null) | ||||
|                   Text( | ||||
|                     widget.poll.title!, | ||||
|                     style: Theme.of(context).textTheme.titleLarge, | ||||
|                   ), | ||||
|                 if (widget.poll.description != null) | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.only(top: 4), | ||||
|                     child: Text( | ||||
|                       widget.poll.description!, | ||||
|                       style: Theme.of(context).textTheme.bodyMedium?.copyWith( | ||||
|                             color: Theme.of( | ||||
|                               context, | ||||
|                             ).textTheme.bodyMedium?.color?.withOpacity(0.7), | ||||
|                           ), | ||||
|                     ), | ||||
|                   ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         for (final q in _questions) | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.only(bottom: 16.0), | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     Expanded( | ||||
|                       child: Text( | ||||
|                         q.title, | ||||
|                         style: Theme.of(context).textTheme.titleMedium, | ||||
|                       ), | ||||
|                     ), | ||||
|                     if (q.isRequired) | ||||
|                       Padding( | ||||
|                         padding: const EdgeInsets.only(left: 8), | ||||
|                         child: Text( | ||||
|                           '*', | ||||
|                           style: Theme.of(context).textTheme.titleMedium?.copyWith( | ||||
|                                 color: Theme.of(context).colorScheme.error, | ||||
|                               ), | ||||
|                         ), | ||||
|                       ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 if (q.description != null) | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.only(top: 4), | ||||
|                     child: Text( | ||||
|                       q.description!, | ||||
|                       style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||
|                             color: Theme.of( | ||||
|                               context, | ||||
|                             ).textTheme.bodySmall?.color?.withOpacity(0.7), | ||||
|                           ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 _buildStats(context, q), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (_questions.isEmpty) { | ||||
|       return const SizedBox.shrink(); | ||||
|     } | ||||
|  | ||||
|     if (widget.isReadonly) { | ||||
|       return _buildReadonlyView(context); | ||||
|     } | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|       children: [ | ||||
| @@ -647,10 +731,9 @@ class _BarStatRow extends StatelessWidget { | ||||
|     final bgColor = Theme.of( | ||||
|       context, | ||||
|     ).colorScheme.surfaceVariant.withOpacity(0.6); | ||||
|     final fg = | ||||
|         (fraction.isNaN || fraction.isInfinite) | ||||
|             ? 0.0 | ||||
|             : fraction.clamp(0.0, 1.0); | ||||
|     final fg = (fraction.isNaN || fraction.isInfinite) | ||||
|         ? 0.0 | ||||
|         : fraction.clamp(0.0, 1.0); | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
| @@ -711,4 +794,4 @@ class _AnimatedStep extends StatelessWidget { | ||||
|       child: child, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -7,11 +7,9 @@ import 'package:island/models/post.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/services/time.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/content/cloud_file_collection.dart'; | ||||
| import 'package:island/widgets/content/markdown.dart'; | ||||
| import 'package:island/widgets/post/post_item.dart'; | ||||
| import 'package:island/widgets/post/post_shared.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:super_context_menu/super_context_menu.dart'; | ||||
|  | ||||
| class PostItemCreator extends HookConsumerWidget { | ||||
| @@ -81,7 +79,6 @@ class PostItemCreator extends HookConsumerWidget { | ||||
|               title: 'copyLink'.tr(), | ||||
|               image: MenuImage.icon(Symbols.link), | ||||
|               callback: () { | ||||
|                 // Copy post link to clipboard | ||||
|                 context.pushNamed( | ||||
|                   'postDetail', | ||||
|                   pathParameters: {'id': item.id}, | ||||
| @@ -105,8 +102,9 @@ class PostItemCreator extends HookConsumerWidget { | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 _buildPostHeader(context), | ||||
|                 _buildPostContent(context), | ||||
|                 PostHeader(item: item), | ||||
|                 PostBody(item: item), | ||||
|                 ReferencedPostWidget(item: item), | ||||
|                 const Gap(16), | ||||
|                 _buildAnalyticsSection(context), | ||||
|               ], | ||||
| @@ -117,128 +115,12 @@ class PostItemCreator extends HookConsumerWidget { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildPostHeader(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         // Post ID and timestamp row | ||||
|         Row( | ||||
|           children: [ | ||||
|             Container( | ||||
|               padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||
|               decoration: BoxDecoration( | ||||
|                 color: Theme.of(context).colorScheme.primaryContainer, | ||||
|                 borderRadius: BorderRadius.circular(4), | ||||
|               ), | ||||
|               child: Text( | ||||
|                 'ID: ${item.id.substring(0, 6)}', | ||||
|                 style: TextStyle( | ||||
|                   fontSize: 12, | ||||
|                   fontWeight: FontWeight.bold, | ||||
|                   color: Theme.of(context).colorScheme.onPrimaryContainer, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             const Spacer(), | ||||
|             Icon( | ||||
|               _getVisibilityIcon(item.visibility), | ||||
|               size: 16, | ||||
|               color: Theme.of(context).colorScheme.secondary, | ||||
|             ), | ||||
|             const SizedBox(width: 4), | ||||
|             Text( | ||||
|               _getVisibilityText(item.visibility).tr(), | ||||
|               style: TextStyle( | ||||
|                 fontSize: 12, | ||||
|                 color: Theme.of(context).colorScheme.secondary, | ||||
|               ), | ||||
|             ), | ||||
|             const Gap(8), | ||||
|             Text( | ||||
|               item.publishedAt?.formatSystem() ?? '', | ||||
|               style: TextStyle( | ||||
|                 fontSize: 12, | ||||
|                 color: Theme.of(context).colorScheme.secondary, | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         const Gap(8), | ||||
|  | ||||
|         // Title and description | ||||
|         if (item.title?.isNotEmpty ?? false) | ||||
|           Text( | ||||
|             item.title!, | ||||
|             style: Theme.of( | ||||
|               context, | ||||
|             ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), | ||||
|           ), | ||||
|         if (item.description?.isNotEmpty ?? false) | ||||
|           Text( | ||||
|             item.description!, | ||||
|             style: Theme.of(context).textTheme.bodyMedium?.copyWith( | ||||
|               color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||
|             ), | ||||
|           ).padding(top: 4), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildPostContent(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         // Content preview | ||||
|         if (item.content?.isNotEmpty ?? false) | ||||
|           Container( | ||||
|             margin: const EdgeInsets.only(top: 12), | ||||
|             child: MarkdownTextContent(content: item.content!), | ||||
|           ), | ||||
|  | ||||
|         // Attachments | ||||
|         if (item.attachments.isNotEmpty) | ||||
|           CloudFileList( | ||||
|             files: item.attachments, | ||||
|             maxWidth: MediaQuery.of(context).size.width * 0.85, | ||||
|             padding: EdgeInsets.only(top: 8), | ||||
|           ), | ||||
|  | ||||
|         // Reference post indicator | ||||
|         if (item.repliedPost != null || item.forwardedPost != null) | ||||
|           Container( | ||||
|             margin: const EdgeInsets.only(top: 8), | ||||
|             child: Row( | ||||
|               children: [ | ||||
|                 Icon( | ||||
|                   item.repliedPost != null ? Symbols.reply : Symbols.forward, | ||||
|                   size: 16, | ||||
|                   color: Theme.of(context).colorScheme.secondary, | ||||
|                 ), | ||||
|                 const Gap(4), | ||||
|                 Text( | ||||
|                   item.repliedPost != null | ||||
|                       ? 'repliedTo'.tr() | ||||
|                       : 'forwarded'.tr(), | ||||
|                   style: TextStyle( | ||||
|                     fontSize: 12, | ||||
|                     color: Theme.of(context).colorScheme.secondary, | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildAnalyticsSection(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Text('Analytics', style: Theme.of(context).textTheme.titleSmall), | ||||
|         const Gap(8), | ||||
|  | ||||
|         // Engagement metrics in a card | ||||
|         Card( | ||||
|           elevation: 1, | ||||
|           margin: EdgeInsets.zero, | ||||
| @@ -279,15 +161,9 @@ class PostItemCreator extends HookConsumerWidget { | ||||
|           ), | ||||
|         ), | ||||
|         const Gap(16), | ||||
|  | ||||
|         // Reactions summary | ||||
|         if (item.reactionsCount.isNotEmpty) _buildReactionsSection(context), | ||||
|  | ||||
|         // Metadata section | ||||
|         if (item.meta != null && item.meta!.isNotEmpty) | ||||
|           _buildMetadataSection(context), | ||||
|  | ||||
|         // Creation and modification timestamps | ||||
|         const Gap(16), | ||||
|         Row( | ||||
|           mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
| @@ -425,31 +301,3 @@ class PostItemCreator extends HookConsumerWidget { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Helper method to get the appropriate icon for each visibility status | ||||
| IconData _getVisibilityIcon(int visibility) { | ||||
|   switch (visibility) { | ||||
|     case 1: // Friends | ||||
|       return Symbols.group; | ||||
|     case 2: // Unlisted | ||||
|       return Symbols.link_off; | ||||
|     case 3: // Private | ||||
|       return Symbols.lock; | ||||
|     default: // Public (0) or unknown | ||||
|       return Symbols.public; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Helper method to get the translation key for each visibility status | ||||
| String _getVisibilityText(int visibility) { | ||||
|   switch (visibility) { | ||||
|     case 1: // Friends | ||||
|       return 'postVisibilityFriends'; | ||||
|     case 2: // Unlisted | ||||
|       return 'postVisibilityUnlisted'; | ||||
|     case 3: // Private | ||||
|       return 'postVisibilityPrivate'; | ||||
|     default: // Public (0) or unknown | ||||
|       return 'postVisibilityPublic'; | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										72
									
								
								lib/widgets/post/post_item_screenshot.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								lib/widgets/post/post_item_screenshot.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/post.dart'; | ||||
| import 'package:island/widgets/post/post_shared.dart'; | ||||
|  | ||||
| class PostItemScreenshot extends ConsumerWidget { | ||||
|   final SnPost item; | ||||
|   final EdgeInsets? padding; | ||||
|   final bool isFullPost; | ||||
|   final bool isShowReference; | ||||
|   const PostItemScreenshot({ | ||||
|     super.key, | ||||
|     required this.item, | ||||
|     this.padding, | ||||
|     this.isFullPost = false, | ||||
|     this.isShowReference = true, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final renderingPadding = | ||||
|         padding ?? const EdgeInsets.symmetric(horizontal: 8, vertical: 8); | ||||
|  | ||||
|     final mostReaction = | ||||
|         item.reactionsCount.isEmpty | ||||
|             ? null | ||||
|             : item.reactionsCount.entries | ||||
|                 .sortedBy((e) => e.value) | ||||
|                 .map((e) => e.key) | ||||
|                 .last; | ||||
|  | ||||
|     return Column( | ||||
|       mainAxisSize: MainAxisSize.min, | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         PostHeader( | ||||
|           item: item, | ||||
|           isFullPost: isFullPost, | ||||
|           isInteractive: false, | ||||
|           renderingPadding: renderingPadding, | ||||
|           trailing: | ||||
|               mostReaction != null | ||||
|                   ? Row( | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         kReactionTemplates[mostReaction]?.icon ?? '', | ||||
|                         style: const TextStyle(fontSize: 20), | ||||
|                       ), | ||||
|                       const Gap(4), | ||||
|                       Text( | ||||
|                         'x${item.reactionsCount[mostReaction]}', | ||||
|                         style: const TextStyle(fontSize: 11), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ) | ||||
|                   : null, | ||||
|         ), | ||||
|         PostBody( | ||||
|           item: item, | ||||
|           renderingPadding: renderingPadding, | ||||
|           isFullPost: isFullPost, | ||||
|           isTextSelectable: false, | ||||
|           isInteractive: false, | ||||
|         ), | ||||
|         if (isShowReference) | ||||
|           ReferencedPostWidget(item: item, isInteractive: false), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										818
									
								
								lib/widgets/post/post_shared.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										818
									
								
								lib/widgets/post/post_shared.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,818 @@ | ||||
| import 'dart:math' as math; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/embed.dart'; | ||||
| import 'package:island/models/poll.dart'; | ||||
| import 'package:island/models/post.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/services/time.dart'; | ||||
| import 'package:island/utils/mapping.dart'; | ||||
| import 'package:island/widgets/account/account_name.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/content/cloud_file_collection.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/content/embed/link.dart'; | ||||
| import 'package:island/widgets/content/markdown.dart'; | ||||
| import 'package:island/widgets/poll/poll_submit.dart'; | ||||
| import 'package:island/widgets/post/post_replies_sheet.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| part 'post_shared.g.dart'; | ||||
|  | ||||
| @riverpod | ||||
| Future<SnPost?> postFeaturedReply(Ref ref, String id) async { | ||||
|   final client = ref.watch(apiClientProvider); | ||||
|   try { | ||||
|     final resp = await client.get('/sphere/posts/$id/replies/featured'); | ||||
|     return SnPost.fromJson(resp.data); | ||||
|   } catch (_) { | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PostVisibilityHelpers { | ||||
|   static IconData getVisibilityIcon(int visibility) { | ||||
|     switch (visibility) { | ||||
|       case 1: | ||||
|         return Symbols.group; | ||||
|       case 2: | ||||
|         return Symbols.link_off; | ||||
|       case 3: | ||||
|         return Symbols.lock; | ||||
|       default: | ||||
|         return Symbols.public; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   static String getVisibilityText(int visibility) { | ||||
|     switch (visibility) { | ||||
|       case 1: | ||||
|         return 'postVisibilityFriends'; | ||||
|       case 2: | ||||
|         return 'postVisibilityUnlisted'; | ||||
|       case 3: | ||||
|         return 'postVisibilityPrivate'; | ||||
|       default: | ||||
|         return 'postVisibilityPublic'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PostReplyPreview extends HookConsumerWidget { | ||||
|   final SnPost parent; | ||||
|   final bool isOpenable; | ||||
|   final bool isCompact; | ||||
|   final bool isAutoload; | ||||
|   final VoidCallback? onOpen; | ||||
|   const PostReplyPreview({ | ||||
|     super.key, | ||||
|     required this.parent, | ||||
|     this.isOpenable = false, | ||||
|     this.isCompact = false, | ||||
|     this.isAutoload = true, | ||||
|     this.onOpen, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final posts = useState<List<SnPost>>([]); | ||||
|     final loading = useState(false); | ||||
|  | ||||
|     Future<void> fetchMoreReplies({int pageSize = 3}) async { | ||||
|       final client = ref.read(apiClientProvider); | ||||
|       loading.value = true; | ||||
|  | ||||
|       try { | ||||
|         final response = await client.get( | ||||
|           '/sphere/posts/${parent.id}/replies', | ||||
|           queryParameters: {'offset': posts.value.length, 'take': pageSize}, | ||||
|         ); | ||||
|         try { | ||||
|           posts.value = [ | ||||
|             ...posts.value, | ||||
|             ...response.data.map((e) => SnPost.fromJson(e)), | ||||
|           ]; | ||||
|         } catch (_) { | ||||
|           // ignore disposed | ||||
|         } | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         try { | ||||
|           loading.value = false; | ||||
|         } catch (_) { | ||||
|           // ignore disposed | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     useEffect(() { | ||||
|       if (isAutoload) fetchMoreReplies(); | ||||
|       return null; | ||||
|     }, [parent]); | ||||
|  | ||||
|     final featuredReply = | ||||
|         isOpenable ? null : ref.watch(PostFeaturedReplyProvider(parent.id)); | ||||
|  | ||||
|     final itemWidget = | ||||
|         isOpenable | ||||
|             ? Column( | ||||
|               children: [ | ||||
|                 for (final post in posts.value) | ||||
|                   Column( | ||||
|                     children: [ | ||||
|                       InkWell( | ||||
|                         child: Row( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           spacing: 8, | ||||
|                           children: [ | ||||
|                             ProfilePictureWidget( | ||||
|                               file: post.publisher.picture, | ||||
|                               radius: 12, | ||||
|                             ).padding(top: 4), | ||||
|                             if (post.content?.isNotEmpty ?? false) | ||||
|                               Expanded( | ||||
|                                 child: MarkdownTextContent( | ||||
|                                   content: post.content!, | ||||
|                                 ).padding(top: 2), | ||||
|                               ) | ||||
|                             else | ||||
|                               Expanded( | ||||
|                                 child: Text( | ||||
|                                   'postHasAttachments', | ||||
|                                 ).plural(post.attachments.length), | ||||
|                               ), | ||||
|                           ], | ||||
|                         ), | ||||
|                         onTap: () { | ||||
|                           onOpen?.call(); | ||||
|                           context.pushNamed( | ||||
|                             'postDetail', | ||||
|                             pathParameters: {'id': post.id}, | ||||
|                           ); | ||||
|                         }, | ||||
|                       ), | ||||
|                       if (post.repliesCount > 0) | ||||
|                         PostReplyPreview( | ||||
|                           parent: post, | ||||
|                           isOpenable: true, | ||||
|                           isCompact: true, | ||||
|                           isAutoload: false, | ||||
|                           onOpen: onOpen, | ||||
|                         ).padding(left: 24), | ||||
|                     ], | ||||
|                   ), | ||||
|                 if (loading.value) | ||||
|                   Row( | ||||
|                     spacing: 8, | ||||
|                     children: [ | ||||
|                       SizedBox( | ||||
|                         width: 16, | ||||
|                         height: 16, | ||||
|                         child: CircularProgressIndicator(), | ||||
|                       ), | ||||
|                       Text('loading').tr(), | ||||
|                     ], | ||||
|                   ) | ||||
|                 else if (posts.value.length < parent.repliesCount) | ||||
|                   InkWell( | ||||
|                     child: Row( | ||||
|                       spacing: 8, | ||||
|                       children: [ | ||||
|                         const Icon(Symbols.keyboard_arrow_down, size: 20), | ||||
|                         Text('repliesLoadMore').tr(), | ||||
|                       ], | ||||
|                     ), | ||||
|                     onTap: () { | ||||
|                       fetchMoreReplies(); | ||||
|                     }, | ||||
|                   ), | ||||
|               ], | ||||
|             ) | ||||
|             : (featuredReply!).map( | ||||
|               data: | ||||
|                   (data) => Row( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                     spacing: 8, | ||||
|                     children: [ | ||||
|                       ProfilePictureWidget( | ||||
|                         file: data.value?.publisher.picture, | ||||
|                         radius: 12, | ||||
|                       ).padding(top: 4), | ||||
|                       if (data.value?.content?.isNotEmpty ?? false) | ||||
|                         Expanded( | ||||
|                           child: MarkdownTextContent( | ||||
|                             content: data.value!.content!, | ||||
|                           ), | ||||
|                         ) | ||||
|                       else | ||||
|                         Expanded( | ||||
|                           child: Text( | ||||
|                             'postHasAttachments', | ||||
|                           ).plural(data.value?.attachments.length ?? 0), | ||||
|                         ), | ||||
|                     ], | ||||
|                   ), | ||||
|               error: | ||||
|                   (e) => Row( | ||||
|                     spacing: 8, | ||||
|                     children: [ | ||||
|                       const Icon(Symbols.close, size: 18), | ||||
|                       Text(e.error.toString()), | ||||
|                     ], | ||||
|                   ), | ||||
|               loading: | ||||
|                   (_) => Row( | ||||
|                     spacing: 8, | ||||
|                     children: [ | ||||
|                       SizedBox( | ||||
|                         width: 16, | ||||
|                         height: 16, | ||||
|                         child: CircularProgressIndicator(), | ||||
|                       ), | ||||
|                       Text('loading').tr(), | ||||
|                     ], | ||||
|                   ), | ||||
|             ); | ||||
|  | ||||
|     final contentWidget = | ||||
|         isCompact | ||||
|             ? itemWidget | ||||
|             : Container( | ||||
|               padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), | ||||
|               decoration: BoxDecoration( | ||||
|                 color: Theme.of(context).colorScheme.surfaceContainerLow, | ||||
|                 border: Border.all( | ||||
|                   color: Theme.of(context).dividerColor.withOpacity(0.5), | ||||
|                 ), | ||||
|                 borderRadius: BorderRadius.all(Radius.circular(8)), | ||||
|               ), | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                 spacing: 4, | ||||
|                 children: [ | ||||
|                   Text('repliesCount') | ||||
|                       .plural(parent.repliesCount) | ||||
|                       .fontSize(15) | ||||
|                       .bold() | ||||
|                       .padding(horizontal: 5), | ||||
|                   itemWidget, | ||||
|                 ], | ||||
|               ), | ||||
|             ); | ||||
|  | ||||
|     return InkWell( | ||||
|       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|       onTap: () { | ||||
|         showModalBottomSheet( | ||||
|           context: context, | ||||
|           isScrollControlled: true, | ||||
|           useRootNavigator: true, | ||||
|           builder: (context) => PostRepliesSheet(post: parent), | ||||
|         ); | ||||
|       }, | ||||
|       child: contentWidget, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PostTruncateHint extends StatelessWidget { | ||||
|   final bool isCompact; | ||||
|   final EdgeInsets? margin; | ||||
|   final bool withArrow; | ||||
|  | ||||
|   const PostTruncateHint({ | ||||
|     super.key, | ||||
|     this.isCompact = false, | ||||
|     this.margin, | ||||
|     this.withArrow = false, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Container( | ||||
|       margin: margin ?? EdgeInsets.only(top: isCompact ? 4 : 8), | ||||
|       padding: EdgeInsets.symmetric( | ||||
|         horizontal: isCompact ? 8 : 12, | ||||
|         vertical: isCompact ? 4 : 8, | ||||
|       ), | ||||
|       decoration: BoxDecoration( | ||||
|         color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), | ||||
|         borderRadius: BorderRadius.circular(8), | ||||
|         border: Border.all( | ||||
|           color: Theme.of(context).colorScheme.outline.withOpacity(0.2), | ||||
|         ), | ||||
|       ), | ||||
|       child: Row( | ||||
|         mainAxisSize: MainAxisSize.min, | ||||
|         children: [ | ||||
|           Icon( | ||||
|             Symbols.more_horiz, | ||||
|             size: isCompact ? 14 : 16, | ||||
|             color: Theme.of(context).colorScheme.secondary, | ||||
|           ), | ||||
|           SizedBox(width: isCompact ? 4 : 6), | ||||
|           Flexible( | ||||
|             child: Text( | ||||
|               'postTruncated'.tr(), | ||||
|               style: TextStyle( | ||||
|                 fontSize: isCompact ? 10 : 12, | ||||
|                 color: Theme.of(context).colorScheme.secondary, | ||||
|                 fontStyle: FontStyle.italic, | ||||
|               ), | ||||
|               maxLines: 1, | ||||
|               overflow: TextOverflow.ellipsis, | ||||
|             ), | ||||
|           ), | ||||
|           if (withArrow) ...[ | ||||
|             SizedBox(width: isCompact ? 3 : 4), | ||||
|             Icon( | ||||
|               Symbols.arrow_forward, | ||||
|               size: isCompact ? 12 : 14, | ||||
|               color: Theme.of(context).colorScheme.secondary, | ||||
|             ), | ||||
|           ], | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class ReferencedPostWidget extends StatelessWidget { | ||||
|   final SnPost item; | ||||
|   final bool isInteractive; | ||||
|  | ||||
|   const ReferencedPostWidget({ | ||||
|     super.key, | ||||
|     required this.item, | ||||
|     this.isInteractive = true, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final referencePost = item.repliedPost ?? item.forwardedPost; | ||||
|     if (referencePost == null) return const SizedBox.shrink(); | ||||
|  | ||||
|     final isReply = item.repliedPost != null; | ||||
|  | ||||
|     final content = Container( | ||||
|       padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), | ||||
|       margin: const EdgeInsets.only(top: 8), | ||||
|       decoration: BoxDecoration( | ||||
|         color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5), | ||||
|         borderRadius: BorderRadius.circular(12), | ||||
|         border: Border.all( | ||||
|           color: Theme.of(context).dividerColor.withOpacity(0.5), | ||||
|         ), | ||||
|       ), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Row( | ||||
|             children: [ | ||||
|               Icon( | ||||
|                 isReply ? Symbols.reply : Symbols.forward, | ||||
|                 size: 16, | ||||
|                 color: Theme.of(context).colorScheme.secondary, | ||||
|               ), | ||||
|               const SizedBox(width: 6), | ||||
|               Text( | ||||
|                 isReply ? 'repliedTo'.tr() : 'forwarded'.tr(), | ||||
|                 style: TextStyle( | ||||
|                   color: Theme.of(context).colorScheme.secondary, | ||||
|                   fontWeight: FontWeight.w500, | ||||
|                   fontSize: 12, | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           const SizedBox(height: 8), | ||||
|           Row( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               ProfilePictureWidget( | ||||
|                 fileId: referencePost.publisher.picture?.id, | ||||
|                 radius: 16, | ||||
|               ), | ||||
|               const SizedBox(width: 8), | ||||
|               Expanded( | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Text( | ||||
|                       referencePost.publisher.nick, | ||||
|                       style: const TextStyle( | ||||
|                         fontWeight: FontWeight.bold, | ||||
|                         fontSize: 14, | ||||
|                       ), | ||||
|                     ), | ||||
|                     if (referencePost.visibility != 0) | ||||
|                       Row( | ||||
|                         mainAxisSize: MainAxisSize.min, | ||||
|                         children: [ | ||||
|                           Icon( | ||||
|                             PostVisibilityHelpers.getVisibilityIcon( | ||||
|                               referencePost.visibility, | ||||
|                             ), | ||||
|                             size: 12, | ||||
|                             color: Theme.of(context).colorScheme.secondary, | ||||
|                           ), | ||||
|                           const SizedBox(width: 4), | ||||
|                           Text( | ||||
|                             PostVisibilityHelpers.getVisibilityText( | ||||
|                               referencePost.visibility, | ||||
|                             ).tr(), | ||||
|                             style: TextStyle( | ||||
|                               fontSize: 10, | ||||
|                               color: Theme.of(context).colorScheme.secondary, | ||||
|                             ), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ).padding(top: 2, bottom: 2), | ||||
|                     if (referencePost.title?.isNotEmpty ?? false) | ||||
|                       Text( | ||||
|                         referencePost.title!, | ||||
|                         style: TextStyle( | ||||
|                           fontWeight: FontWeight.bold, | ||||
|                           fontSize: 13, | ||||
|                           color: Theme.of(context).colorScheme.onSurface, | ||||
|                         ), | ||||
|                       ).padding(top: 2, bottom: 2), | ||||
|                     if (referencePost.description?.isNotEmpty ?? false) | ||||
|                       Text( | ||||
|                         referencePost.description!, | ||||
|                         style: TextStyle( | ||||
|                           fontSize: 12, | ||||
|                           color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||
|                         ), | ||||
|                         maxLines: 2, | ||||
|                         overflow: TextOverflow.ellipsis, | ||||
|                       ).padding(bottom: 2), | ||||
|                     if (referencePost.content?.isNotEmpty ?? false) | ||||
|                       MarkdownTextContent( | ||||
|                         content: referencePost.content!, | ||||
|                         textStyle: const TextStyle(fontSize: 14), | ||||
|                         isSelectable: false, | ||||
|                         linesMargin: | ||||
|                             referencePost.type == 0 | ||||
|                                 ? const EdgeInsets.only(bottom: 4) | ||||
|                                 : null, | ||||
|                         attachments: item.attachments, | ||||
|                       ).padding(bottom: 4), | ||||
|                     if (referencePost.isTruncated) | ||||
|                       const PostTruncateHint( | ||||
|                         isCompact: true, | ||||
|                         margin: EdgeInsets.only(top: 4, bottom: 8), | ||||
|                       ), | ||||
|                     if (referencePost.attachments.isNotEmpty && | ||||
|                         referencePost.type != 1) | ||||
|                       Row( | ||||
|                         mainAxisSize: MainAxisSize.min, | ||||
|                         children: [ | ||||
|                           Icon( | ||||
|                             Symbols.attach_file, | ||||
|                             size: 12, | ||||
|                             color: Theme.of(context).colorScheme.secondary, | ||||
|                           ), | ||||
|                           const SizedBox(width: 4), | ||||
|                           Text( | ||||
|                             'postHasAttachments'.plural( | ||||
|                               referencePost.attachments.length, | ||||
|                             ), | ||||
|                             style: TextStyle( | ||||
|                               color: Theme.of(context).colorScheme.secondary, | ||||
|                               fontSize: 12, | ||||
|                             ), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ).padding(vertical: 2), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     if (!isInteractive) { | ||||
|       return content; | ||||
|     } | ||||
|  | ||||
|     return content.gestures( | ||||
|       onTap: | ||||
|           () => context.pushNamed( | ||||
|             'postDetail', | ||||
|             pathParameters: {'id': referencePost.id}, | ||||
|           ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PostHeader extends StatelessWidget { | ||||
|   final SnPost item; | ||||
|   final bool isFullPost; | ||||
|   final Widget? trailing; | ||||
|   final bool isInteractive; | ||||
|   final EdgeInsets renderingPadding; | ||||
|  | ||||
|   const PostHeader({ | ||||
|     super.key, | ||||
|     required this.item, | ||||
|     this.isFullPost = false, | ||||
|     this.trailing, | ||||
|     this.isInteractive = true, | ||||
|     this.renderingPadding = EdgeInsets.zero, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Row( | ||||
|       crossAxisAlignment: CrossAxisAlignment.center, | ||||
|       spacing: 12, | ||||
|       children: [ | ||||
|         GestureDetector( | ||||
|           onTap: | ||||
|               isInteractive | ||||
|                   ? () { | ||||
|                     context.pushNamed( | ||||
|                       'publisherProfile', | ||||
|                       pathParameters: {'name': item.publisher.name}, | ||||
|                     ); | ||||
|                   } | ||||
|                   : null, | ||||
|           child: ProfilePictureWidget(file: item.publisher.picture, radius: 16), | ||||
|         ), | ||||
|         Expanded( | ||||
|           child: Column( | ||||
|             mainAxisSize: MainAxisSize.min, | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               Row( | ||||
|                 spacing: 4, | ||||
|                 children: [ | ||||
|                   Text(item.publisher.nick).bold(), | ||||
|                   if (item.publisher.verification != null) | ||||
|                     VerificationMark(mark: item.publisher.verification!), | ||||
|                   Text('@${item.publisher.name}').fontSize(11), | ||||
|                 ], | ||||
|               ), | ||||
|               Row( | ||||
|                 spacing: 6, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.end, | ||||
|                 children: [ | ||||
|                   Text( | ||||
|                     isFullPost | ||||
|                         ? (item.publishedAt ?? item.createdAt)!.formatSystem() | ||||
|                         : (item.publishedAt ?? item.createdAt)!.formatRelative( | ||||
|                           context, | ||||
|                         ), | ||||
|                   ).fontSize(10), | ||||
|                   if (item.editedAt != null) | ||||
|                     Text( | ||||
|                       'editedAt'.tr(args: [item.editedAt!.formatSystem()]), | ||||
|                     ).fontSize(10), | ||||
|                   if (item.visibility != 0) | ||||
|                     Text( | ||||
|                       PostVisibilityHelpers.getVisibilityText( | ||||
|                         item.visibility, | ||||
|                       ).tr(), | ||||
|                     ).fontSize(10), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|         if (trailing != null) trailing!, | ||||
|       ], | ||||
|     ).padding(horizontal: renderingPadding.horizontal, bottom: 4); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PostBody extends ConsumerWidget { | ||||
|   final SnPost item; | ||||
|   final bool isFullPost; | ||||
|   final bool isTextSelectable; | ||||
|   final Widget? translationSection; | ||||
|   final bool isInteractive; | ||||
|   final EdgeInsets renderingPadding; | ||||
|  | ||||
|   const PostBody({ | ||||
|     super.key, | ||||
|     required this.item, | ||||
|     this.isFullPost = false, | ||||
|     this.isTextSelectable = true, | ||||
|     this.translationSection, | ||||
|     this.isInteractive = true, | ||||
|     this.renderingPadding = EdgeInsets.zero, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         if (!isFullPost && item.type == 1) | ||||
|           Container( | ||||
|             decoration: BoxDecoration( | ||||
|               border: Border.all( | ||||
|                 color: Theme.of(context).dividerColor.withOpacity(0.5), | ||||
|               ), | ||||
|               borderRadius: const BorderRadius.all(Radius.circular(16)), | ||||
|             ), | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), | ||||
|             margin: const EdgeInsets.only(top: 4), | ||||
|             child: Column( | ||||
|               mainAxisSize: MainAxisSize.min, | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 Align( | ||||
|                   alignment: Alignment.centerLeft, | ||||
|                   child: Badge( | ||||
|                     label: const Text('postArticle').tr(), | ||||
|                     backgroundColor: Theme.of(context).colorScheme.primary, | ||||
|                     textColor: Theme.of(context).colorScheme.onPrimary, | ||||
|                   ), | ||||
|                 ), | ||||
|                 const Gap(4), | ||||
|                 if (item.title != null) | ||||
|                   Text( | ||||
|                     item.title!, | ||||
|                     style: Theme.of(context).textTheme.titleMedium!.copyWith( | ||||
|                       fontWeight: FontWeight.bold, | ||||
|                     ), | ||||
|                   ), | ||||
|                 if (item.description != null) | ||||
|                   Text( | ||||
|                     item.description!, | ||||
|                     style: Theme.of(context).textTheme.bodyMedium, | ||||
|                   ) | ||||
|                 else | ||||
|                   MarkdownTextContent(content: '${item.content!}...'), | ||||
|               ], | ||||
|             ), | ||||
|           ) | ||||
|         else if ((item.content?.isNotEmpty ?? false) || | ||||
|             (item.title?.isNotEmpty ?? false) || | ||||
|             (item.description?.isNotEmpty ?? false)) | ||||
|           Padding( | ||||
|             padding: EdgeInsets.only( | ||||
|               left: renderingPadding.horizontal, | ||||
|               right: renderingPadding.horizontal, | ||||
|             ), | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 if ((item.title?.isNotEmpty ?? false) || | ||||
|                     (item.description?.isNotEmpty ?? false)) | ||||
|                   Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       if (item.title?.isNotEmpty ?? false) | ||||
|                         Text( | ||||
|                           item.title!, | ||||
|                           style: Theme.of(context).textTheme.titleMedium! | ||||
|                               .copyWith(fontWeight: FontWeight.bold), | ||||
|                         ), | ||||
|                       if (item.description?.isNotEmpty ?? false) | ||||
|                         Text( | ||||
|                           item.description!, | ||||
|                           style: Theme.of(context).textTheme.bodyMedium, | ||||
|                         ), | ||||
|                     ], | ||||
|                   ).padding(bottom: 4), | ||||
|                 MarkdownTextContent( | ||||
|                   content: | ||||
|                       item.isTruncated ? '${item.content!}...' : item.content!, | ||||
|                   isSelectable: isTextSelectable, | ||||
|                 ), | ||||
|                 if (translationSection != null) translationSection!, | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         if (item.isTruncated && item.type != 1) | ||||
|           PostTruncateHint( | ||||
|             isCompact: true, | ||||
|             withArrow: isInteractive, | ||||
|             margin: EdgeInsets.only( | ||||
|               top: 4, | ||||
|               bottom: 4, | ||||
|               left: renderingPadding.horizontal, | ||||
|               right: renderingPadding.horizontal, | ||||
|             ), | ||||
|           ), | ||||
|         if (item.attachments.isNotEmpty && item.type != 1) | ||||
|           CloudFileList( | ||||
|             files: item.attachments, | ||||
|             padding: EdgeInsets.symmetric( | ||||
|               horizontal: renderingPadding.horizontal, | ||||
|               vertical: 4, | ||||
|             ), | ||||
|           ), | ||||
|         if (item.tags.isNotEmpty || item.categories.isNotEmpty) | ||||
|           Column( | ||||
|             mainAxisSize: MainAxisSize.min, | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             spacing: 2, | ||||
|             children: [ | ||||
|               if (item.tags.isNotEmpty) | ||||
|                 Wrap( | ||||
|                   runAlignment: WrapAlignment.center, | ||||
|                   spacing: 8, | ||||
|                   children: [ | ||||
|                     const Icon(Symbols.label, size: 16).padding(top: 2), | ||||
|                     for (final tag | ||||
|                         in isFullPost ? item.tags : item.tags.take(3)) | ||||
|                       InkWell( | ||||
|                         onTap: | ||||
|                             isInteractive | ||||
|                                 ? () { | ||||
|                                   GoRouter.of(context).pushNamed( | ||||
|                                     'postTagDetail', | ||||
|                                     pathParameters: {'slug': tag.slug}, | ||||
|                                   ); | ||||
|                                 } | ||||
|                                 : null, | ||||
|                         child: Text('#${tag.name ?? tag.slug}'), | ||||
|                       ), | ||||
|                     if (!isFullPost && item.tags.length > 3) | ||||
|                       Text('+${item.tags.length - 3}').opacity(0.6), | ||||
|                   ], | ||||
|                 ), | ||||
|               if (item.categories.isNotEmpty) | ||||
|                 Wrap( | ||||
|                   runAlignment: WrapAlignment.center, | ||||
|                   spacing: 8, | ||||
|                   children: [ | ||||
|                     const Icon(Symbols.category, size: 16).padding(top: 2), | ||||
|                     for (final category | ||||
|                         in isFullPost | ||||
|                             ? item.categories | ||||
|                             : item.categories.take(2)) | ||||
|                       InkWell( | ||||
|                         onTap: | ||||
|                             isInteractive | ||||
|                                 ? () { | ||||
|                                   GoRouter.of(context).pushNamed( | ||||
|                                     'postCategoryDetail', | ||||
|                                     pathParameters: {'slug': category.slug}, | ||||
|                                   ); | ||||
|                                 } | ||||
|                                 : null, | ||||
|                         child: Text(category.categoryDisplayTitle), | ||||
|                       ), | ||||
|                     if (!isFullPost && item.categories.length > 2) | ||||
|                       Text('+${item.categories.length - 2}').opacity(0.6), | ||||
|                   ], | ||||
|                 ), | ||||
|             ], | ||||
|           ).padding(horizontal: renderingPadding.horizontal + 4, top: 4), | ||||
|         if (item.meta?['embeds'] != null) | ||||
|           ...((item.meta!['embeds'] as List<dynamic>) | ||||
|               .map((embedData) => convertMapKeysToSnakeCase(embedData)) | ||||
|               .map( | ||||
|                 (embedData) => switch (embedData['type']) { | ||||
|                   'link' => EmbedLinkWidget( | ||||
|                     link: SnScrappedLink.fromJson(embedData), | ||||
|                     maxWidth: math.min( | ||||
|                       MediaQuery.of(context).size.width, | ||||
|                       kWideScreenWidth, | ||||
|                     ), | ||||
|                     margin: EdgeInsets.only( | ||||
|                       top: 4, | ||||
|                       bottom: 4, | ||||
|                       left: renderingPadding.horizontal, | ||||
|                       right: renderingPadding.horizontal, | ||||
|                     ), | ||||
|                   ), | ||||
|                   'poll' => Card( | ||||
|                     margin: EdgeInsets.symmetric( | ||||
|                       horizontal: renderingPadding.horizontal, | ||||
|                       vertical: 8, | ||||
|                     ), | ||||
|                     child: | ||||
|                         embedData['poll'] == null | ||||
|                             ? const Text('Poll was not loaded...') | ||||
|                             : PollSubmit( | ||||
|                               initialAnswers: | ||||
|                                   embedData['poll']?['user_answer']?['answer'], | ||||
|                               stats: embedData['poll']?['stats'], | ||||
|                               poll: SnPollWithStats.fromJson(embedData['poll']), | ||||
|                               onSubmit: (_) {}, | ||||
|                               isReadonly: !isInteractive, | ||||
|                             ).padding(horizontal: 16, vertical: 12), | ||||
|                   ), | ||||
|                   _ => Text('Unable show embed: ${embedData['type']}'), | ||||
|                 }, | ||||
|               )), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| 
 | ||||
| part of 'post_item.dart'; | ||||
| part of 'post_shared.dart'; | ||||
| 
 | ||||
| // ************************************************************************** | ||||
| // RiverpodGenerator | ||||
| @@ -10,7 +10,9 @@ import connectivity_plus | ||||
| import device_info_plus | ||||
| import file_picker | ||||
| import file_selector_macos | ||||
| import firebase_analytics | ||||
| import firebase_core | ||||
| import firebase_crashlytics | ||||
| import firebase_messaging | ||||
| import flutter_inappwebview_macos | ||||
| import flutter_platform_alert | ||||
| @@ -44,7 +46,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | ||||
|   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) | ||||
|   FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) | ||||
|   FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) | ||||
|   FirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseAnalyticsPlugin")) | ||||
|   FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) | ||||
|   FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) | ||||
|   FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) | ||||
|   InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) | ||||
|   FlutterPlatformAlertPlugin.register(with: registry.registrar(forPlugin: "FlutterPlatformAlertPlugin")) | ||||
|   | ||||
| @@ -13,23 +13,64 @@ PODS: | ||||
|     - FlutterMacOS | ||||
|   - Firebase/CoreOnly (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|   - Firebase/Crashlytics (12.0.0): | ||||
|     - Firebase/CoreOnly | ||||
|     - FirebaseCrashlytics (~> 12.0.0) | ||||
|   - Firebase/Messaging (12.0.0): | ||||
|     - Firebase/CoreOnly | ||||
|     - FirebaseMessaging (~> 12.0.0) | ||||
|   - firebase_analytics (12.0.0): | ||||
|     - firebase_core | ||||
|     - FirebaseAnalytics (= 12.0.0) | ||||
|     - FlutterMacOS | ||||
|   - firebase_core (4.0.0): | ||||
|     - Firebase/CoreOnly (~> 12.0.0) | ||||
|     - FlutterMacOS | ||||
|   - firebase_crashlytics (5.0.0): | ||||
|     - Firebase/CoreOnly (~> 12.0.0) | ||||
|     - Firebase/Crashlytics (~> 12.0.0) | ||||
|     - firebase_core | ||||
|     - FlutterMacOS | ||||
|   - firebase_messaging (16.0.0): | ||||
|     - Firebase/CoreOnly (~> 12.0.0) | ||||
|     - Firebase/Messaging (~> 12.0.0) | ||||
|     - firebase_core | ||||
|     - FlutterMacOS | ||||
|   - FirebaseAnalytics (12.0.0): | ||||
|     - FirebaseAnalytics/Default (= 12.0.0) | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - FirebaseAnalytics/Default (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - GoogleAppMeasurement/Default (= 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - FirebaseCore (12.0.0): | ||||
|     - FirebaseCoreInternal (~> 12.0.0) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - GoogleUtilities/Logger (~> 8.1) | ||||
|   - FirebaseCoreExtension (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|   - FirebaseCoreInternal (12.0.0): | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|   - FirebaseCrashlytics (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - FirebaseRemoteConfigInterop (~> 12.0.0) | ||||
|     - FirebaseSessions (~> 12.0.0) | ||||
|     - GoogleDataTransport (~> 10.1) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesObjC (~> 2.4) | ||||
|   - FirebaseInstallations (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
| @@ -44,6 +85,16 @@ PODS: | ||||
|     - GoogleUtilities/Reachability (~> 8.1) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - FirebaseRemoteConfigInterop (12.0.0) | ||||
|   - FirebaseSessions (12.0.0): | ||||
|     - FirebaseCore (~> 12.0.0) | ||||
|     - FirebaseCoreExtension (~> 12.0.0) | ||||
|     - FirebaseInstallations (~> 12.0.0) | ||||
|     - GoogleDataTransport (~> 10.1) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesSwift (~> 2.1) | ||||
|   - flutter_inappwebview_macos (0.0.1): | ||||
|     - FlutterMacOS | ||||
|     - OrderedSet (~> 6.0.3) | ||||
| @@ -63,6 +114,28 @@ PODS: | ||||
|   - gal (1.0.0): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - GoogleAppMeasurement/Core (12.0.0): | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - GoogleAppMeasurement/Default (12.0.0): | ||||
|     - GoogleAdsOnDeviceConversion (= 2.1.0) | ||||
|     - GoogleAppMeasurement/Core (= 12.0.0) | ||||
|     - GoogleAppMeasurement/IdentitySupport (= 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - GoogleAppMeasurement/IdentitySupport (12.0.0): | ||||
|     - GoogleAppMeasurement/Core (= 12.0.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Network (~> 8.1) | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - GoogleDataTransport (10.1.0): | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesObjC (~> 2.4) | ||||
| @@ -76,6 +149,9 @@ PODS: | ||||
|   - GoogleUtilities/Logger (8.1.0): | ||||
|     - GoogleUtilities/Environment | ||||
|     - GoogleUtilities/Privacy | ||||
|   - GoogleUtilities/MethodSwizzler (8.1.0): | ||||
|     - GoogleUtilities/Logger | ||||
|     - GoogleUtilities/Privacy | ||||
|   - GoogleUtilities/Network (8.1.0): | ||||
|     - GoogleUtilities/Logger | ||||
|     - "GoogleUtilities/NSData+zlib" | ||||
| @@ -117,6 +193,8 @@ PODS: | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - PromisesObjC (2.4.0) | ||||
|   - PromisesSwift (2.4.0): | ||||
|     - PromisesObjC (= 2.4.0) | ||||
|   - record_macos (1.0.0): | ||||
|     - FlutterMacOS | ||||
|   - SAMKeychain (1.5.3) | ||||
| @@ -172,7 +250,9 @@ DEPENDENCIES: | ||||
|   - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) | ||||
|   - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) | ||||
|   - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) | ||||
|   - firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`) | ||||
|   - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) | ||||
|   - firebase_crashlytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos`) | ||||
|   - firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`) | ||||
|   - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) | ||||
|   - flutter_platform_alert (from `Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos`) | ||||
| @@ -204,15 +284,22 @@ DEPENDENCIES: | ||||
| SPEC REPOS: | ||||
|   trunk: | ||||
|     - Firebase | ||||
|     - FirebaseAnalytics | ||||
|     - FirebaseCore | ||||
|     - FirebaseCoreExtension | ||||
|     - FirebaseCoreInternal | ||||
|     - FirebaseCrashlytics | ||||
|     - FirebaseInstallations | ||||
|     - FirebaseMessaging | ||||
|     - FirebaseRemoteConfigInterop | ||||
|     - FirebaseSessions | ||||
|     - GoogleAppMeasurement | ||||
|     - GoogleDataTransport | ||||
|     - GoogleUtilities | ||||
|     - nanopb | ||||
|     - OrderedSet | ||||
|     - PromisesObjC | ||||
|     - PromisesSwift | ||||
|     - SAMKeychain | ||||
|     - sqlite3 | ||||
|     - WebRTC-SDK | ||||
| @@ -230,8 +317,12 @@ EXTERNAL SOURCES: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos | ||||
|   file_selector_macos: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos | ||||
|   firebase_analytics: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos | ||||
|   firebase_core: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos | ||||
|   firebase_crashlytics: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos | ||||
|   firebase_messaging: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos | ||||
|   flutter_inappwebview_macos: | ||||
| @@ -295,12 +386,19 @@ SPEC CHECKSUMS: | ||||
|   file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a | ||||
|   file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 | ||||
|   Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 | ||||
|   firebase_analytics: 53f0dc87ad10f56a6df8746da60d8a5fe41f886f | ||||
|   firebase_core: eeea10f64026b68cd0bc3dee079ab4717e22909e | ||||
|   firebase_crashlytics: 7be1dacc38809971354def57193b280636a3d51a | ||||
|   firebase_messaging: 5eefcd5bde556bfacdd9968e11c52f39032dfbe5 | ||||
|   FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7 | ||||
|   FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a | ||||
|   FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361 | ||||
|   FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55 | ||||
|   FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7 | ||||
|   FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988 | ||||
|   FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde | ||||
|   FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613 | ||||
|   FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42 | ||||
|   flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d | ||||
|   flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284 | ||||
|   flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54 | ||||
| @@ -309,6 +407,7 @@ SPEC CHECKSUMS: | ||||
|   flutter_webrtc: 0d70bd8782c19bde286dc52f766eebbea26de201 | ||||
|   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 | ||||
|   gal: baecd024ebfd13c441269ca7404792a7152fde89 | ||||
|   GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3 | ||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||
|   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 | ||||
|   irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba | ||||
| @@ -322,6 +421,7 @@ SPEC CHECKSUMS: | ||||
|   pasteboard: 278d8100149f940fb795d6b3a74f0720c890ecb7 | ||||
|   path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 | ||||
|   PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 | ||||
|   PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 | ||||
|   record_macos: 295d70bd5fb47145df78df7b80e6697cd18403c0 | ||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||
|   share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc | ||||
|   | ||||
| @@ -234,6 +234,7 @@ | ||||
| 				3399D490228B24CF009A79C7 /* ShellScript */, | ||||
| 				F1E275A871246799FC3019F6 /* [CP] Embed Pods Frameworks */, | ||||
| 				8D06F41203F1FD2FDE04DC7F /* [CP] Copy Pods Resources */, | ||||
| 				6B512DBE9D8E74A09686E70F /* FlutterFire: "flutterfire upload-crashlytics-symbols" */, | ||||
| 			); | ||||
| 			buildRules = ( | ||||
| 			); | ||||
| @@ -376,6 +377,24 @@ | ||||
| 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; | ||||
| 			showEnvVarsInLog = 0; | ||||
| 		}; | ||||
| 		6B512DBE9D8E74A09686E70F /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			inputFileListPaths = ( | ||||
| 			); | ||||
| 			inputPaths = ( | ||||
| 			); | ||||
| 			name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\""; | ||||
| 			outputFileListPaths = ( | ||||
| 			); | ||||
| 			outputPaths = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 			shellPath = /bin/sh; | ||||
| 			shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n  # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n  DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n  PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n  PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=macos --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n"; | ||||
| 		}; | ||||
| 		8D06F41203F1FD2FDE04DC7F /* [CP] Copy Pods Resources */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
|   | ||||
							
								
								
									
										54
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -557,10 +557,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: file_picker | ||||
|       sha256: "8f9f429998f9232d65bc4757af74475ce44fc80f10704ff5dfa8b1d14fc429b9" | ||||
|       sha256: "970d33d79e1da667b6da222575fd7f2e30e323ca76251504477e6d51405b2d9a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.2.3" | ||||
|     version: "10.2.4" | ||||
|   file_selector_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -593,6 +593,30 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.9.3+4" | ||||
|   firebase_analytics: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: firebase_analytics | ||||
|       sha256: "07146e89e11302c6b07e3465c2c556ebcdd0053a3c5b1aa9bfd3203b778e5b4c" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "12.0.0" | ||||
|   firebase_analytics_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: firebase_analytics_platform_interface | ||||
|       sha256: "27e81a0efc821bec6cba64abc1083b91c8ddbad28eeb4c6f6b7c78a59d06f259" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.0.0" | ||||
|   firebase_analytics_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: firebase_analytics_web | ||||
|       sha256: "7d87f47462042a7d9125e3123db2783bc72917d85e2719d4cb6aeaec209605e1" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.6.0" | ||||
|   firebase_core: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -617,6 +641,22 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.0" | ||||
|   firebase_crashlytics: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: firebase_crashlytics | ||||
|       sha256: "95b6871850b1a7e3b09c284c59a0c71fafcad3eee8ac1b6f06aaf8979290cbb8" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.0.0" | ||||
|   firebase_crashlytics_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: firebase_crashlytics_platform_interface | ||||
|       sha256: ba5b7a916f1ebedc6db35b33abdc618f202fc25e0792088dfba698e19fec9c09 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.8.11" | ||||
|   firebase_messaging: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1430,7 +1470,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "0.12.17" | ||||
|   material_color_utilities: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: material_color_utilities | ||||
|       sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec | ||||
| @@ -1981,6 +2021,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.0" | ||||
|   screenshot: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: screenshot | ||||
|       sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.0" | ||||
|   scroll_to_index: | ||||
|     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: 3.1.0+122 | ||||
| version: 3.2.0+124 | ||||
|  | ||||
| environment: | ||||
|   sdk: ^3.7.2 | ||||
| @@ -73,7 +73,7 @@ dependencies: | ||||
|     git: https://github.com/LittleSheep2Code/tus_client.git | ||||
|   cross_file: ^0.3.4+2 | ||||
|   image_picker: ^1.1.2 | ||||
|   file_picker: ^10.2.3 | ||||
|   file_picker: ^10.2.4 | ||||
|   riverpod_annotation: ^2.6.1 | ||||
|   image_picker_platform_interface: ^2.10.1 | ||||
|   image_picker_android: ^0.8.12+25 | ||||
| @@ -134,6 +134,10 @@ dependencies: | ||||
|   flutter_langdetect: ^0.0.2 | ||||
|   waveform_flutter: ^1.2.0 | ||||
|   flutter_app_update: ^3.2.2 | ||||
|   firebase_crashlytics: ^5.0.0 | ||||
|   firebase_analytics: ^12.0.0 | ||||
|   material_color_utilities: ^0.11.1 | ||||
|   screenshot: ^3.0.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user