Compare commits
25 Commits
2ea9f5e907
...
v3
Author | SHA1 | Date | |
---|---|---|---|
a7454edec0 | |||
cbf1952eb7 | |||
6d06f0a1b4 | |||
f2d2a9efd8 | |||
d44c8217b0 | |||
446c33d8b0 | |||
996462f1fd | |||
778f6bb79f | |||
8747f948b9 | |||
9546d6e4b8 | |||
f8d1940af6 | |||
b2b0891d24 | |||
274168d4bc | |||
2c98b348d5 | |||
afc7887ddd | |||
99ff78a3d5 | |||
2ad85addf6 | |||
552b4b2572 | |||
594ac39e3d | |||
23321171f3 | |||
ee72d79c93 | |||
a20c2598fc | |||
2eba871a6d | |||
46919dec31 | |||
9dd6cffe0c |
@ -42,6 +42,15 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Deeplinking -->
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" android:host="solian.app" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Share Intent Filters -->
|
<!-- Share Intent Filters -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
@ -375,7 +375,9 @@
|
|||||||
"postContent": "Content",
|
"postContent": "Content",
|
||||||
"postSettings": "Settings",
|
"postSettings": "Settings",
|
||||||
"postPublisherUnselected": "Publisher Unspecified",
|
"postPublisherUnselected": "Publisher Unspecified",
|
||||||
"postVisibility": "Visibility",
|
"postType": "Post Type",
|
||||||
|
"articleAttachmentHint": "Attachments must be uploaded and inserted into the article body to be visible.",
|
||||||
|
"postVisibility": "Post Visibility",
|
||||||
"postVisibilityPublic": "Public",
|
"postVisibilityPublic": "Public",
|
||||||
"postVisibilityFriends": "Friends Only",
|
"postVisibilityFriends": "Friends Only",
|
||||||
"postVisibilityUnlisted": "Unlisted",
|
"postVisibilityUnlisted": "Unlisted",
|
||||||
@ -588,6 +590,7 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"navigateToChat": "Navigate to Chat",
|
"navigateToChat": "Navigate to Chat",
|
||||||
"wouldYouLikeToNavigateToChat": "Would You like to navigate to the chat?",
|
"wouldYouLikeToNavigateToChat": "Would You like to navigate to the chat?",
|
||||||
|
"abuseReports": "Abuse Reports",
|
||||||
"abuseReport": "Report",
|
"abuseReport": "Report",
|
||||||
"abuseReportTitle": "Report Content",
|
"abuseReportTitle": "Report Content",
|
||||||
"abuseReportDescription": "Help us keep the community safe by reporting inappropriate content or behavior.",
|
"abuseReportDescription": "Help us keep the community safe by reporting inappropriate content or behavior.",
|
||||||
@ -686,8 +689,9 @@
|
|||||||
"aboutScreenDeveloperSectionTitle": "Developer",
|
"aboutScreenDeveloperSectionTitle": "Developer",
|
||||||
"aboutScreenContactUsTitle": "Contact Us",
|
"aboutScreenContactUsTitle": "Contact Us",
|
||||||
"aboutScreenLicenseTitle": "License",
|
"aboutScreenLicenseTitle": "License",
|
||||||
"aboutScreenLicenseContent": "All copyright reserved © {} Solsynth\nOpen-sourced under license GNU AGPL v3.0",
|
"aboutScreenLicenseContent": "GNU Affero General Public License v3.0",
|
||||||
"aboutScreenCopyright": "© {} {}. All rights reserved.",
|
"aboutScreenCopyright": "All rights reserved © Solsynth {}",
|
||||||
|
"aboutScreenMadeWith": "Made with ❤︎️ by Solar Network Team",
|
||||||
"aboutScreenFailedToLoadPackageInfo": "Failed to load package info: {error}",
|
"aboutScreenFailedToLoadPackageInfo": "Failed to load package info: {error}",
|
||||||
"copiedToClipboard": "Copied to clipboard",
|
"copiedToClipboard": "Copied to clipboard",
|
||||||
"copyToClipboardTooltip": "Copy to clipboard",
|
"copyToClipboardTooltip": "Copy to clipboard",
|
||||||
|
@ -512,5 +512,33 @@
|
|||||||
"orderId": "订单 ID",
|
"orderId": "订单 ID",
|
||||||
"enterOrderId": "输入您的订单 ID",
|
"enterOrderId": "输入您的订单 ID",
|
||||||
"restore": "恢复",
|
"restore": "恢复",
|
||||||
"keyboardShortcuts": "键盘快捷键"
|
"keyboardShortcuts": "键盘快捷键",
|
||||||
|
"about": "关于",
|
||||||
|
"membershipCancel": "取消会员订阅",
|
||||||
|
"membershipCancelConfirm": "您确定要取消您的会员订阅?",
|
||||||
|
"membershipCancelHint": "您确定要取消您的会员订阅吗?您将不会再被收费。您的会员资格将在当前计费周期结束前保持有效。并且您在当前订阅结束之前无法重新订阅。",
|
||||||
|
"membershipCancelSuccess": "您的会员订阅已成功取消。",
|
||||||
|
"aboutScreenTitle": "关于",
|
||||||
|
"aboutScreenVersionInfo": "版本 {} ({})",
|
||||||
|
"aboutScreenAppInfoSectionTitle": "应用信息",
|
||||||
|
"aboutScreenPackageNameLabel": "包名",
|
||||||
|
"aboutScreenVersionLabel": "版本",
|
||||||
|
"aboutScreenBuildNumberLabel": "构建编号",
|
||||||
|
"aboutScreenLinksSectionTitle": "链接",
|
||||||
|
"aboutScreenPrivacyPolicyTitle": "隐私政策",
|
||||||
|
"aboutScreenTermsOfServiceTitle": "服务条款",
|
||||||
|
"aboutScreenOpenSourceLicensesTitle": "开源许可证",
|
||||||
|
"aboutScreenDeveloperSectionTitle": "开发者",
|
||||||
|
"aboutScreenContactUsTitle": "联系我们",
|
||||||
|
"aboutScreenLicenseTitle": "许可证",
|
||||||
|
"aboutScreenLicenseContent": "GNU Affero General Public License v3.0",
|
||||||
|
"aboutScreenCopyright": "版权所有 © 索尔辛茨 {}",
|
||||||
|
"aboutScreenMadeWith": "由 Solar Network Team 用 ❤︎️ 制作",
|
||||||
|
"aboutScreenFailedToLoadPackageInfo": "加载包信息失败:{error}",
|
||||||
|
"copiedToClipboard": "已复制到剪贴板",
|
||||||
|
"copyToClipboardTooltip": "复制到剪贴板",
|
||||||
|
"postForwardingTo": "转发给",
|
||||||
|
"postReplyingTo": "回复给",
|
||||||
|
"postEditing": "您正在编辑现有帖子",
|
||||||
|
"postArticle": "文章"
|
||||||
}
|
}
|
||||||
|
@ -45,10 +45,10 @@ PODS:
|
|||||||
- Firebase/Messaging (11.15.0):
|
- Firebase/Messaging (11.15.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.15.0)
|
- FirebaseMessaging (~> 11.15.0)
|
||||||
- firebase_core (3.15.0):
|
- firebase_core (3.15.1):
|
||||||
- Firebase/CoreOnly (= 11.15.0)
|
- Firebase/CoreOnly (= 11.15.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.2.8):
|
- firebase_messaging (15.2.9):
|
||||||
- Firebase/Messaging (= 11.15.0)
|
- Firebase/Messaging (= 11.15.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -130,7 +130,7 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- irondash_engine_context (0.0.1):
|
- irondash_engine_context (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Kingfisher (8.3.3)
|
- Kingfisher (8.4.0)
|
||||||
- livekit_client (2.4.9):
|
- livekit_client (2.4.9):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
@ -178,18 +178,18 @@ PODS:
|
|||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (3.50.1):
|
- sqlite3 (3.50.2):
|
||||||
- sqlite3/common (= 3.50.1)
|
- sqlite3/common (= 3.50.2)
|
||||||
- sqlite3/common (3.50.1)
|
- sqlite3/common (3.50.2)
|
||||||
- sqlite3/dbstatvtab (3.50.1):
|
- sqlite3/dbstatvtab (3.50.2):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.50.1):
|
- sqlite3/fts5 (3.50.2):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/math (3.50.1):
|
- sqlite3/math (3.50.2):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.50.1):
|
- sqlite3/perf-threadsafe (3.50.2):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.50.1):
|
- sqlite3/rtree (3.50.2):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -362,8 +362,8 @@ SPEC CHECKSUMS:
|
|||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||||
firebase_core: c727a02c560a53f1f1e56e18f16515eb5753c492
|
firebase_core: ece862f94b2bc72ee0edbeec7ab5c7cb09fe1ab5
|
||||||
firebase_messaging: 4158969b04b667f5435731ec9d6e453bb58b0c4c
|
firebase_messaging: e1a5fae495603115be1d0183bc849da748734e2b
|
||||||
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
||||||
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
||||||
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
||||||
@ -382,9 +382,9 @@ SPEC CHECKSUMS:
|
|||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||||
Kingfisher: ff82cb91d9266ddb56cbb2f72d32c26f00d3e5be
|
Kingfisher: b14cc47bbfa7a3c150dd12962ee9c86338545629
|
||||||
livekit_client: 3f79d79233a5bd13d5b541732624ef959d7c538e
|
livekit_client: 3f79d79233a5bd13d5b541732624ef959d7c538e
|
||||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
@ -403,7 +403,7 @@ SPEC CHECKSUMS:
|
|||||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 1d85290c3321153511f6e900ede7a1608718bbd5
|
sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc
|
||||||
sqlite3_flutter_libs: e7fc8c9ea2200ff3271f08f127842131746b70e2
|
sqlite3_flutter_libs: e7fc8c9ea2200ff3271f08f127842131746b70e2
|
||||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 77;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@ -525,10 +525,14 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@ -586,10 +590,14 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@ -772,6 +780,7 @@
|
|||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -1202,6 +1211,7 @@
|
|||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -1229,6 +1239,7 @@
|
|||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -27,6 +27,7 @@ import UIKit
|
|||||||
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
|
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
|
||||||
|
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Flutter View Controller-->
|
<!--Flutter View Controller-->
|
||||||
@ -14,13 +16,14 @@
|
|||||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
<point key="canvasLocation" x="-26" y="-76"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
@ -2,16 +2,10 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CLIENT_ID</key>
|
<key>AppGroupId</key>
|
||||||
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
<string>$(CUSTOM_GROUP_ID)</string>
|
||||||
<key>REVERSED_CLIENT_ID</key>
|
|
||||||
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
|
||||||
<key>PLIST_VERSION</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>BUNDLE_ID</key>
|
<key>BUNDLE_ID</key>
|
||||||
<string>dev.solsynth.solian</string>
|
<string>dev.solsynth.solian</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
|
||||||
<false/>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
@ -32,8 +26,6 @@
|
|||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@ -45,18 +37,35 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>CLIENT_ID</key>
|
||||||
|
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCalendarsUsageDescription</key>
|
<key>NSCalendarsUsageDescription</key>
|
||||||
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
|
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
|
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
|
||||||
|
<key>NSFaceIDUsageDescription</key>
|
||||||
|
<string>Allow the Solar Network verify your ownership of the logged in account and continue your action quickly.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
|
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
|
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
|
||||||
|
<key>NSUserActivityTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>INStartCallIntent</string>
|
||||||
|
<string>INSendMessageIntent</string>
|
||||||
|
</array>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>REVERSED_CLIENT_ID</key>
|
||||||
|
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
@ -74,25 +83,16 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>Allow the Solar Network verify your ownership of the logged in account and continue your action quickly.</string>
|
|
||||||
<key>AppGroupId</key>
|
|
||||||
<string>$(CUSTOM_GROUP_ID)</string>
|
|
||||||
<key>NSUserActivityTypes</key>
|
|
||||||
<array>
|
|
||||||
<string>INStartCallIntent</string>
|
|
||||||
<string>INSendMessageIntent</string>
|
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -221,7 +221,7 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
Future(() {
|
Future(() {
|
||||||
userNotifier.fetchUser().then((_) {
|
userNotifier.fetchUser().then((_) {
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
if (user.hasValue) {
|
if (user.value != null) {
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
subscribePushNotification(apiClient);
|
subscribePushNotification(apiClient);
|
||||||
final wsNotifier = ref.read(websocketStateProvider.notifier);
|
final wsNotifier = ref.read(websocketStateProvider.notifier);
|
||||||
|
23
lib/models/abuse_report.dart
Normal file
23
lib/models/abuse_report.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'abuse_report.freezed.dart';
|
||||||
|
part 'abuse_report.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnAbuseReport with _$SnAbuseReport {
|
||||||
|
const factory SnAbuseReport({
|
||||||
|
required String id,
|
||||||
|
required String resourceIdentifier,
|
||||||
|
required int type,
|
||||||
|
required String reason,
|
||||||
|
required DateTime? resolvedAt,
|
||||||
|
required String? resolution,
|
||||||
|
required String accountId,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnAbuseReport;
|
||||||
|
|
||||||
|
factory SnAbuseReport.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnAbuseReportFromJson(json);
|
||||||
|
}
|
175
lib/models/abuse_report.freezed.dart
Normal file
175
lib/models/abuse_report.freezed.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'abuse_report.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnAbuseReport {
|
||||||
|
|
||||||
|
String get id; String get resourceIdentifier; int get type; String get reason; DateTime? get resolvedAt; String? get resolution; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnAbuseReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAbuseReportCopyWith<SnAbuseReport> get copyWith => _$SnAbuseReportCopyWithImpl<SnAbuseReport>(this as SnAbuseReport, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnAbuseReport to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAbuseReport&&(identical(other.id, id) || other.id == id)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.type, type) || other.type == type)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.resolvedAt, resolvedAt) || other.resolvedAt == resolvedAt)&&(identical(other.resolution, resolution) || other.resolution == resolution)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,resourceIdentifier,type,reason,resolvedAt,resolution,accountId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnAbuseReport(id: $id, resourceIdentifier: $resourceIdentifier, type: $type, reason: $reason, resolvedAt: $resolvedAt, resolution: $resolution, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnAbuseReportCopyWith<$Res> {
|
||||||
|
factory $SnAbuseReportCopyWith(SnAbuseReport value, $Res Function(SnAbuseReport) _then) = _$SnAbuseReportCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String resourceIdentifier, int type, String reason, DateTime? resolvedAt, String? resolution, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnAbuseReportCopyWithImpl<$Res>
|
||||||
|
implements $SnAbuseReportCopyWith<$Res> {
|
||||||
|
_$SnAbuseReportCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnAbuseReport _self;
|
||||||
|
final $Res Function(SnAbuseReport) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnAbuseReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? resourceIdentifier = null,Object? type = null,Object? reason = null,Object? resolvedAt = freezed,Object? resolution = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,resolvedAt: freezed == resolvedAt ? _self.resolvedAt : resolvedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,resolution: freezed == resolution ? _self.resolution : resolution // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnAbuseReport implements SnAbuseReport {
|
||||||
|
const _SnAbuseReport({required this.id, required this.resourceIdentifier, required this.type, required this.reason, required this.resolvedAt, required this.resolution, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
|
factory _SnAbuseReport.fromJson(Map<String, dynamic> json) => _$SnAbuseReportFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String resourceIdentifier;
|
||||||
|
@override final int type;
|
||||||
|
@override final String reason;
|
||||||
|
@override final DateTime? resolvedAt;
|
||||||
|
@override final String? resolution;
|
||||||
|
@override final String accountId;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnAbuseReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnAbuseReportCopyWith<_SnAbuseReport> get copyWith => __$SnAbuseReportCopyWithImpl<_SnAbuseReport>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnAbuseReportToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAbuseReport&&(identical(other.id, id) || other.id == id)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.type, type) || other.type == type)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.resolvedAt, resolvedAt) || other.resolvedAt == resolvedAt)&&(identical(other.resolution, resolution) || other.resolution == resolution)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,resourceIdentifier,type,reason,resolvedAt,resolution,accountId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnAbuseReport(id: $id, resourceIdentifier: $resourceIdentifier, type: $type, reason: $reason, resolvedAt: $resolvedAt, resolution: $resolution, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnAbuseReportCopyWith<$Res> implements $SnAbuseReportCopyWith<$Res> {
|
||||||
|
factory _$SnAbuseReportCopyWith(_SnAbuseReport value, $Res Function(_SnAbuseReport) _then) = __$SnAbuseReportCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String resourceIdentifier, int type, String reason, DateTime? resolvedAt, String? resolution, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnAbuseReportCopyWithImpl<$Res>
|
||||||
|
implements _$SnAbuseReportCopyWith<$Res> {
|
||||||
|
__$SnAbuseReportCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnAbuseReport _self;
|
||||||
|
final $Res Function(_SnAbuseReport) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnAbuseReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? resourceIdentifier = null,Object? type = null,Object? reason = null,Object? resolvedAt = freezed,Object? resolution = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnAbuseReport(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,resolvedAt: freezed == resolvedAt ? _self.resolvedAt : resolvedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,resolution: freezed == resolution ? _self.resolution : resolution // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
41
lib/models/abuse_report.g.dart
Normal file
41
lib/models/abuse_report.g.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'abuse_report.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnAbuseReport _$SnAbuseReportFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnAbuseReport(
|
||||||
|
id: json['id'] as String,
|
||||||
|
resourceIdentifier: json['resource_identifier'] as String,
|
||||||
|
type: (json['type'] as num).toInt(),
|
||||||
|
reason: json['reason'] as String,
|
||||||
|
resolvedAt:
|
||||||
|
json['resolved_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['resolved_at'] as String),
|
||||||
|
resolution: json['resolution'] as String?,
|
||||||
|
accountId: json['account_id'] as String,
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnAbuseReportToJson(_SnAbuseReport instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'resource_identifier': instance.resourceIdentifier,
|
||||||
|
'type': instance.type,
|
||||||
|
'reason': instance.reason,
|
||||||
|
'resolved_at': instance.resolvedAt?.toIso8601String(),
|
||||||
|
'resolution': instance.resolution,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
38
lib/models/abuse_report_type.dart
Normal file
38
lib/models/abuse_report_type.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
enum AbuseReportType {
|
||||||
|
copyright(0),
|
||||||
|
harassment(1),
|
||||||
|
impersonation(2),
|
||||||
|
offensiveContent(3),
|
||||||
|
spam(4),
|
||||||
|
privacyViolation(5),
|
||||||
|
illegalContent(6),
|
||||||
|
other(7);
|
||||||
|
|
||||||
|
const AbuseReportType(this.value);
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
static AbuseReportType fromValue(int value) {
|
||||||
|
return values.firstWhere((e) => e.value == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get displayName {
|
||||||
|
switch (this) {
|
||||||
|
case AbuseReportType.copyright:
|
||||||
|
return 'Copyright';
|
||||||
|
case AbuseReportType.harassment:
|
||||||
|
return 'Harassment';
|
||||||
|
case AbuseReportType.impersonation:
|
||||||
|
return 'Impersonation';
|
||||||
|
case AbuseReportType.offensiveContent:
|
||||||
|
return 'Offensive Content';
|
||||||
|
case AbuseReportType.spam:
|
||||||
|
return 'Spam';
|
||||||
|
case AbuseReportType.privacyViolation:
|
||||||
|
return 'Privacy Violation';
|
||||||
|
case AbuseReportType.illegalContent:
|
||||||
|
return 'Illegal Content';
|
||||||
|
case AbuseReportType.other:
|
||||||
|
return 'Other';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,12 +65,13 @@ final apiClientProvider = Provider<Dio>((ref) {
|
|||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final dio = Dio(
|
final dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl: serverUrl,
|
baseUrl: '$serverUrl/api',
|
||||||
connectTimeout: const Duration(seconds: 10),
|
connectTimeout: const Duration(seconds: 10),
|
||||||
receiveTimeout: const Duration(seconds: 10),
|
receiveTimeout: const Duration(seconds: 10),
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'X-Client': 'Solian',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -18,8 +18,13 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
|||||||
final user = SnAccount.fromJson(response.data);
|
final user = SnAccount.fromJson(response.data);
|
||||||
state = AsyncValue.data(user);
|
state = AsyncValue.data(user);
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
log("[UserInfo] Failed to fetch user info: $error");
|
log(
|
||||||
state = AsyncValue.error(error, stackTrace);
|
"[UserInfo] Failed to fetch user info...",
|
||||||
|
name: 'UserInfoNotifier',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
state = AsyncValue.data(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import 'package:island/screens/posts/post_search.dart';
|
|||||||
import 'package:island/widgets/app_wrapper.dart';
|
import 'package:island/widgets/app_wrapper.dart';
|
||||||
import 'package:island/screens/tabs.dart';
|
import 'package:island/screens/tabs.dart';
|
||||||
import 'package:island/screens/explore.dart';
|
import 'package:island/screens/explore.dart';
|
||||||
import 'package:island/screens/article_detail_screen.dart';
|
import 'package:island/screens/discovery/article_detail.dart';
|
||||||
import 'package:island/screens/account.dart';
|
import 'package:island/screens/account.dart';
|
||||||
import 'package:island/screens/notification.dart';
|
import 'package:island/screens/notification.dart';
|
||||||
import 'package:island/screens/wallet.dart';
|
import 'package:island/screens/wallet.dart';
|
||||||
@ -41,6 +41,8 @@ import 'package:island/screens/realm/realms.dart';
|
|||||||
import 'package:island/screens/realm/realm_detail.dart';
|
import 'package:island/screens/realm/realm_detail.dart';
|
||||||
import 'package:island/screens/account/event_calendar.dart';
|
import 'package:island/screens/account/event_calendar.dart';
|
||||||
import 'package:island/screens/discovery/realms.dart';
|
import 'package:island/screens/discovery/realms.dart';
|
||||||
|
import 'package:island/screens/reports/report_detail.dart';
|
||||||
|
import 'package:island/screens/reports/report_list.dart';
|
||||||
|
|
||||||
// Shell route keys for nested navigation
|
// Shell route keys for nested navigation
|
||||||
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
@ -258,6 +260,19 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
builder: (context, state) => const AboutScreen(),
|
builder: (context, state) => const AboutScreen(),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
GoRoute(
|
||||||
|
path: '/safety/reports/me',
|
||||||
|
builder: (context, state) => const AbuseReportListScreen(),
|
||||||
|
),
|
||||||
|
|
||||||
|
GoRoute(
|
||||||
|
path: '/safety/reports/me/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return AbuseReportDetailScreen(reportId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
// Main tabs with TabsScreen shell
|
// Main tabs with TabsScreen shell
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
navigatorKey: _tabsShellKey,
|
navigatorKey: _tabsShellKey,
|
||||||
@ -399,7 +414,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
builder: (context, state) => const LevelingScreen(),
|
builder: (context, state) => const LevelingScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/settings',
|
path: '/account/me/settings',
|
||||||
builder: (context, state) => const AccountSettingsScreen(),
|
builder: (context, state) => const AccountSettingsScreen(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,24 +1,35 @@
|
|||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/services/notify.dart';
|
||||||
|
import 'package:island/services/udid.native.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class AboutScreen extends StatefulWidget {
|
class AboutScreen extends ConsumerStatefulWidget {
|
||||||
const AboutScreen({super.key});
|
const AboutScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AboutScreen> createState() => _AboutScreenState();
|
ConsumerState<AboutScreen> createState() => _AboutScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AboutScreenState extends State<AboutScreen> {
|
class _AboutScreenState extends ConsumerState<AboutScreen> {
|
||||||
PackageInfo _packageInfo = PackageInfo(
|
PackageInfo _packageInfo = PackageInfo(
|
||||||
appName: 'Solian',
|
appName: 'Solian',
|
||||||
packageName: 'dev.solsynth.solian',
|
packageName: 'dev.solsynth.solian',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
buildNumber: '1',
|
buildNumber: '1',
|
||||||
);
|
);
|
||||||
|
BaseDeviceInfo? _deviceInfo;
|
||||||
|
String? _deviceUdid;
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
|
|
||||||
@ -26,6 +37,7 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initPackageInfo();
|
_initPackageInfo();
|
||||||
|
_initDeviceInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initPackageInfo() async {
|
Future<void> _initPackageInfo() async {
|
||||||
@ -49,6 +61,25 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _initDeviceInfo() async {
|
||||||
|
try {
|
||||||
|
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||||
|
_deviceInfo = await deviceInfoPlugin.deviceInfo;
|
||||||
|
_deviceUdid = await getUdid();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = 'aboutScreenFailedToLoadDeviceInfo'.tr(
|
||||||
|
args: [e.toString()],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _launchURL(String url) async {
|
Future<void> _launchURL(String url) async {
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
if (await canLaunchUrl(uri)) {
|
if (await canLaunchUrl(uri)) {
|
||||||
@ -60,7 +91,7 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(title: Text('about'.tr()), elevation: 0),
|
appBar: AppBar(title: Text('about'.tr()), elevation: 0),
|
||||||
body:
|
body:
|
||||||
_isLoading
|
_isLoading
|
||||||
@ -108,25 +139,66 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_buildInfoItem(
|
_buildInfoItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.info_outline,
|
icon: Symbols.info,
|
||||||
label: 'aboutScreenPackageNameLabel'.tr(),
|
label: 'aboutScreenPackageNameLabel'.tr(),
|
||||||
value: _packageInfo.packageName,
|
value: _packageInfo.packageName,
|
||||||
),
|
),
|
||||||
_buildInfoItem(
|
_buildInfoItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.update,
|
icon: Symbols.update,
|
||||||
label: 'aboutScreenVersionLabel'.tr(),
|
label: 'aboutScreenVersionLabel'.tr(),
|
||||||
value: _packageInfo.version,
|
value: _packageInfo.version,
|
||||||
),
|
),
|
||||||
_buildInfoItem(
|
_buildInfoItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.build,
|
icon: Symbols.build,
|
||||||
label: 'aboutScreenBuildNumberLabel'.tr(),
|
label: 'aboutScreenBuildNumberLabel'.tr(),
|
||||||
value: _packageInfo.buildNumber,
|
value: _packageInfo.buildNumber,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (_deviceInfo != null) const SizedBox(height: 16),
|
||||||
|
|
||||||
|
if (_deviceInfo != null)
|
||||||
|
_buildSection(
|
||||||
|
context,
|
||||||
|
title: 'Device Information',
|
||||||
|
children: [
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Symbols.label,
|
||||||
|
label: 'Device Name',
|
||||||
|
value: _deviceInfo?.data['name'],
|
||||||
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Symbols.fingerprint,
|
||||||
|
label: 'Device Identifier',
|
||||||
|
value: _deviceUdid ?? 'N/A',
|
||||||
|
copyable: true,
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Symbols.notifications_active,
|
||||||
|
title: 'Reactivate Push Notifications',
|
||||||
|
onTap: () async {
|
||||||
|
showLoadingModal(context);
|
||||||
|
try {
|
||||||
|
await subscribePushNotification(
|
||||||
|
ref.watch(apiClientProvider),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Links Card
|
// Links Card
|
||||||
@ -136,7 +208,7 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Icons.privacy_tip_outlined,
|
icon: Symbols.privacy_tip,
|
||||||
title: 'aboutScreenPrivacyPolicyTitle'.tr(),
|
title: 'aboutScreenPrivacyPolicyTitle'.tr(),
|
||||||
onTap:
|
onTap:
|
||||||
() => _launchURL(
|
() => _launchURL(
|
||||||
@ -145,7 +217,7 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
),
|
),
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Icons.description_outlined,
|
icon: Symbols.description,
|
||||||
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
||||||
onTap:
|
onTap:
|
||||||
() => _launchURL(
|
() => _launchURL(
|
||||||
@ -154,7 +226,7 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
),
|
),
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Icons.code,
|
icon: Symbols.code,
|
||||||
title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
|
title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showLicensePage(
|
showLicensePage(
|
||||||
@ -177,14 +249,14 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Icons.email_outlined,
|
icon: Symbols.email,
|
||||||
title: 'aboutScreenContactUsTitle'.tr(),
|
title: 'aboutScreenContactUsTitle'.tr(),
|
||||||
subtitle: 'lily@solsynth.dev',
|
subtitle: 'lily@solsynth.dev',
|
||||||
onTap: () => _launchURL('mailto:lily@solsynth.dev'),
|
onTap: () => _launchURL('mailto:lily@solsynth.dev'),
|
||||||
),
|
),
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Icons.copyright,
|
icon: Symbols.copyright,
|
||||||
title: 'aboutScreenLicenseTitle'.tr(),
|
title: 'aboutScreenLicenseTitle'.tr(),
|
||||||
subtitle: 'aboutScreenLicenseContent'.tr(
|
subtitle: 'aboutScreenLicenseContent'.tr(
|
||||||
args: [DateTime.now().year.toString()],
|
args: [DateTime.now().year.toString()],
|
||||||
@ -202,14 +274,25 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
// Copyright
|
// Copyright
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Text(
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
'aboutScreenCopyright'.tr(
|
'aboutScreenCopyright'.tr(
|
||||||
args: [DateTime.now().year.toString(), "Solsynth"],
|
args: [DateTime.now().year.toString()],
|
||||||
),
|
),
|
||||||
style: theme.textTheme.bodySmall,
|
style: theme.textTheme.bodySmall,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
const Gap(1),
|
||||||
|
Text(
|
||||||
|
'aboutScreenMadeWith'.tr(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).fontSize(10).opacity(0.8),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -247,6 +330,7 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
required IconData icon,
|
required IconData icon,
|
||||||
required String label,
|
required String label,
|
||||||
required String value,
|
required String value,
|
||||||
|
bool copyable = false,
|
||||||
}) {
|
}) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
@ -263,13 +347,14 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
SelectableText(
|
SelectableText(
|
||||||
value,
|
value,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
maxLines: copyable ? 1 : null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (value.startsWith('http') || value.contains('@'))
|
if (value.startsWith('http') || value.contains('@') || copyable)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.copy, size: 16),
|
icon: const Icon(Symbols.content_copy, size: 16),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(ClipboardData(text: value));
|
Clipboard.setData(ClipboardData(text: value));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@ -301,7 +386,7 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
subtitle: subtitle != null ? Text(subtitle) : null,
|
subtitle: subtitle != null ? Text(subtitle) : null,
|
||||||
isThreeLine: multipleLines,
|
isThreeLine: multipleLines,
|
||||||
trailing: const Icon(
|
trailing: const Icon(
|
||||||
Icons.chevron_right,
|
Symbols.chevron_right,
|
||||||
).padding(top: multipleLines ? 8 : 0),
|
).padding(top: multipleLines ? 8 : 0),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
@ -59,7 +59,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
notificationUnreadCountNotifierProvider,
|
notificationUnreadCountNotifierProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!user.hasValue || user.value == null) {
|
if (user.value == null || user.value == null) {
|
||||||
return _UnauthorizedAccountScreen();
|
return _UnauthorizedAccountScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,9 +222,17 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('relationships').tr(),
|
title: Text('relationships').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.push('/account/relationship');
|
context.push('/account/relationships');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
title: Text('abuseReports').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.gavel),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () => context.push('/safety/reports/me'),
|
||||||
|
),
|
||||||
const Divider(height: 1).padding(vertical: 8),
|
const Divider(height: 1).padding(vertical: 8),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
@ -328,7 +336,7 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
child: Card(
|
child: Card(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.push('/auth/create');
|
context.push('/auth/create-account');
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@ -367,12 +375,23 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.push('/about');
|
||||||
|
},
|
||||||
|
child: Text('about').tr(),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.push('/settings');
|
context.push('/settings');
|
||||||
},
|
},
|
||||||
child: Text('appSettings').tr(),
|
child: Text('appSettings').tr(),
|
||||||
).center(),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).center(),
|
).center(),
|
||||||
|
@ -82,7 +82,7 @@ class EventCalanderScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Show user profile if viewing someone else's calendar
|
// Show user profile if viewing someone else's calendar
|
||||||
if (name != 'me' && user.hasValue)
|
if (name != 'me' && user.value != null)
|
||||||
AccountNameplate(name: name),
|
AccountNameplate(name: name),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -106,7 +106,7 @@ class EventCalanderScreen extends HookConsumerWidget {
|
|||||||
).padding(horizontal: 8, vertical: 4),
|
).padding(horizontal: 8, vertical: 4),
|
||||||
|
|
||||||
// Show user profile if viewing someone else's calendar
|
// Show user profile if viewing someone else's calendar
|
||||||
if (name != 'me' && user.hasValue)
|
if (name != 'me' && user.value != null)
|
||||||
AccountNameplate(name: name),
|
AccountNameplate(name: name),
|
||||||
Gap(MediaQuery.of(context).padding.bottom + 16),
|
Gap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
],
|
],
|
||||||
|
@ -22,6 +22,7 @@ import 'package:island/widgets/account/status.dart';
|
|||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/safety/abuse_report_helper.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@ -72,6 +73,8 @@ Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async {
|
|||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnChatRoom?> accountDirectChat(Ref ref, String uname) async {
|
Future<SnChatRoom?> accountDirectChat(Ref ref, String uname) async {
|
||||||
|
final userInfo = ref.watch(userInfoProvider);
|
||||||
|
if (userInfo.value == null) return null;
|
||||||
final account = await ref.watch(accountProvider(uname).future);
|
final account = await ref.watch(accountProvider(uname).future);
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
try {
|
try {
|
||||||
@ -87,6 +90,8 @@ Future<SnChatRoom?> accountDirectChat(Ref ref, String uname) async {
|
|||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnRelationship?> accountRelationship(Ref ref, String uname) async {
|
Future<SnRelationship?> accountRelationship(Ref ref, String uname) async {
|
||||||
|
final userInfo = ref.watch(userInfoProvider);
|
||||||
|
if (userInfo.value == null) return null;
|
||||||
final account = await ref.watch(accountProvider(uname).future);
|
final account = await ref.watch(accountProvider(uname).future);
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
try {
|
try {
|
||||||
@ -139,6 +144,23 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> blockAction() async {
|
||||||
|
showLoadingModal(context);
|
||||||
|
try {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
if (accountRelationship.value == null) {
|
||||||
|
await client.post('/relationships/${account.value!.id}/block');
|
||||||
|
} else {
|
||||||
|
await client.delete('/relationships/${account.value!.id}/block');
|
||||||
|
}
|
||||||
|
ref.invalidate(accountRelationshipProvider(name));
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> directMessageAction() async {
|
Future<void> directMessageAction() async {
|
||||||
if (!account.hasValue) return;
|
if (!account.hasValue) return;
|
||||||
if (accountChat.value != null) {
|
if (accountChat.value != null) {
|
||||||
@ -219,6 +241,8 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
return account.when(
|
return account.when(
|
||||||
data:
|
data:
|
||||||
(data) => AppScaffold(
|
(data) => AppScaffold(
|
||||||
@ -379,13 +403,19 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
).padding(horizontal: 24),
|
).padding(horizontal: 24),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (user.value != null)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: const Divider(height: 1).padding(top: 24, bottom: 12),
|
child: const Divider(
|
||||||
|
height: 1,
|
||||||
|
).padding(top: 24, bottom: 12),
|
||||||
),
|
),
|
||||||
|
if (user.value != null)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
|
if (accountRelationship.value == null ||
|
||||||
|
accountRelationship.value!.status > -100)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
@ -397,7 +427,9 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
foregroundColor: WidgetStatePropertyAll(
|
foregroundColor: WidgetStatePropertyAll(
|
||||||
accountRelationship.value == null
|
accountRelationship.value == null
|
||||||
? null
|
? null
|
||||||
: Theme.of(context).colorScheme.onSecondary,
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: relationshipAction,
|
onPressed: relationshipAction,
|
||||||
@ -413,6 +445,44 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
: const Icon(Symbols.person_check),
|
: const Icon(Symbols.person_check),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (accountRelationship.value == null ||
|
||||||
|
accountRelationship.value!.status <= -100)
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
foregroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: blockAction,
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? 'blockUser'
|
||||||
|
: 'unblockUser',
|
||||||
|
).tr(),
|
||||||
|
icon:
|
||||||
|
accountRelationship.value == null
|
||||||
|
? const Icon(Symbols.block)
|
||||||
|
: const Icon(Symbols.person_cancel),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: directMessageAction,
|
onPressed: directMessageAction,
|
||||||
@ -426,8 +496,25 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
).tr(),
|
).tr(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
IconButton.filled(
|
||||||
|
onPressed: () {
|
||||||
|
showAbuseReportSheet(
|
||||||
|
context,
|
||||||
|
resourceIdentifier: 'account/${data.id}',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.flag,
|
||||||
|
color: Theme.of(context).colorScheme.onError,
|
||||||
|
),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16, top: 4),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: const Divider(height: 1).padding(top: 12),
|
child: const Divider(height: 1).padding(top: 12),
|
||||||
|
@ -395,7 +395,7 @@ class _AccountAppbarForcegroundColorProviderElement
|
|||||||
String get uname => (origin as AccountAppbarForcegroundColorProvider).uname;
|
String get uname => (origin as AccountAppbarForcegroundColorProvider).uname;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$accountDirectChatHash() => r'60d0015fc2a3c8fc2190bb41d6818cf3027d9d0a';
|
String _$accountDirectChatHash() => r'3d28c8ba8079159f724fe3cd47bbe00db55cedcc';
|
||||||
|
|
||||||
/// See also [accountDirectChat].
|
/// See also [accountDirectChat].
|
||||||
@ProviderFor(accountDirectChat)
|
@ProviderFor(accountDirectChat)
|
||||||
@ -517,7 +517,7 @@ class _AccountDirectChatProviderElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$accountRelationshipHash() =>
|
String _$accountRelationshipHash() =>
|
||||||
r'cb7d0d3f8cd4f23ad9d2d529872c540dac483d4f';
|
r'0be2420e1f6a65b8dcead9617191471924aaf232';
|
||||||
|
|
||||||
/// See also [accountRelationship].
|
/// See also [accountRelationship].
|
||||||
@ProviderFor(accountRelationship)
|
@ProviderFor(accountRelationship)
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:island/widgets/content/markdown.dart';
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
import 'package:island/models/webfeed.dart';
|
|
||||||
import 'package:island/pods/article_detail.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
|
||||||
import 'package:island/widgets/loading_indicator.dart';
|
|
||||||
import 'package:html2md/html2md.dart' as html2md;
|
|
||||||
|
|
||||||
class ArticleDetailScreen extends ConsumerWidget {
|
|
||||||
final String articleId;
|
|
||||||
|
|
||||||
const ArticleDetailScreen({super.key, required this.articleId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final articleAsync = ref.watch(articleDetailProvider(articleId));
|
|
||||||
|
|
||||||
return AppScaffold(
|
|
||||||
body: articleAsync.when(
|
|
||||||
data:
|
|
||||||
(article) => AppScaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: const BackButton(),
|
|
||||||
title: Text(article.title),
|
|
||||||
),
|
|
||||||
body: _ArticleDetailContent(article: article),
|
|
||||||
),
|
|
||||||
loading: () => const Center(child: LoadingIndicator()),
|
|
||||||
error:
|
|
||||||
(error, stackTrace) =>
|
|
||||||
Center(child: Text('Failed to load article: $error')),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ArticleDetailContent extends HookConsumerWidget {
|
|
||||||
final SnWebArticle article;
|
|
||||||
|
|
||||||
const _ArticleDetailContent({required this.article});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final markdownContent = useMemoized(
|
|
||||||
() => html2md.convert(article.content ?? ''),
|
|
||||||
[article],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
if (article.preview?.imageUrl != null)
|
|
||||||
Image.network(
|
|
||||||
article.preview!.imageUrl!,
|
|
||||||
width: double.infinity,
|
|
||||||
height: 200,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
article.title,
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (article.feed?.title != null)
|
|
||||||
Text(
|
|
||||||
article.feed!.title,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 32),
|
|
||||||
if (article.content != null)
|
|
||||||
...MarkdownTextContent.buildGenerator(
|
|
||||||
isDark: Theme.of(context).brightness == Brightness.dark,
|
|
||||||
).buildWidgets(markdownContent)
|
|
||||||
else if (article.preview?.description != null)
|
|
||||||
Text(article.preview!.description!),
|
|
||||||
const Gap(24),
|
|
||||||
FilledButton(
|
|
||||||
onPressed:
|
|
||||||
() => launchUrlString(
|
|
||||||
article.url,
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
),
|
|
||||||
child: const Text('Read Full Article'),
|
|
||||||
),
|
|
||||||
Gap(MediaQuery.of(context).padding.bottom),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,7 +28,7 @@ class StickersScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.push('/creators/stickers/new?pubName=pubName').then((
|
context.push('/creators/stickers/new?pubName=$pubName').then((
|
||||||
value,
|
value,
|
||||||
) {
|
) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:island/models/webfeed.dart';
|
import 'package:island/models/webfeed.dart';
|
||||||
import 'package:island/pods/webfeed.dart';
|
import 'package:island/pods/webfeed.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@ -183,7 +184,7 @@ class WebFeedEditScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}, [pubName, feedId, ref, context]);
|
}, [pubName, feedId, ref, context]);
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'),
|
title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'),
|
||||||
actions: [
|
actions: [
|
||||||
|
110
lib/screens/discovery/article_detail.dart
Normal file
110
lib/screens/discovery/article_detail.dart
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
import 'package:island/models/webfeed.dart';
|
||||||
|
import 'package:island/pods/article_detail.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/loading_indicator.dart';
|
||||||
|
import 'package:html2md/html2md.dart' as html2md;
|
||||||
|
|
||||||
|
class ArticleDetailScreen extends ConsumerWidget {
|
||||||
|
final String articleId;
|
||||||
|
|
||||||
|
const ArticleDetailScreen({super.key, required this.articleId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final articleAsync = ref.watch(articleDetailProvider(articleId));
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
body: articleAsync.when(
|
||||||
|
data:
|
||||||
|
(article) => AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const BackButton(),
|
||||||
|
title: Text(article.title),
|
||||||
|
),
|
||||||
|
body: _ArticleDetailContent(article: article),
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: LoadingIndicator()),
|
||||||
|
error:
|
||||||
|
(error, stackTrace) =>
|
||||||
|
Center(child: Text('Failed to load article: $error')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArticleDetailContent extends HookConsumerWidget {
|
||||||
|
final SnWebArticle article;
|
||||||
|
|
||||||
|
const _ArticleDetailContent({required this.article});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final markdownContent = useMemoized(
|
||||||
|
() => html2md.convert(article.content ?? ''),
|
||||||
|
[article],
|
||||||
|
);
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (article.preview?.imageUrl != null)
|
||||||
|
Image.network(
|
||||||
|
article.preview!.imageUrl!,
|
||||||
|
width: double.infinity,
|
||||||
|
height: 200,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
article.title,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (article.feed?.title != null)
|
||||||
|
Text(
|
||||||
|
article.feed!.title,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 32),
|
||||||
|
if (article.content != null)
|
||||||
|
...MarkdownTextContent.buildGenerator(
|
||||||
|
isDark: Theme.of(context).brightness == Brightness.dark,
|
||||||
|
).buildWidgets(markdownContent)
|
||||||
|
else if (article.preview?.description != null)
|
||||||
|
Text(article.preview!.description!),
|
||||||
|
const Gap(24),
|
||||||
|
FilledButton(
|
||||||
|
onPressed:
|
||||||
|
() => launchUrlString(
|
||||||
|
article.url,
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
),
|
||||||
|
child: const Text('Read Full Article'),
|
||||||
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/webfeed.dart';
|
import 'package:island/models/webfeed.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/web_article_card.dart';
|
import 'package:island/widgets/web_article_card.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@ -124,9 +125,12 @@ class ArticlesScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(title: Text(title ?? 'Articles')),
|
appBar: AppBar(title: Text(title ?? 'Articles')),
|
||||||
body: CustomScrollView(
|
body: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
|
padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
|
||||||
@ -137,6 +141,8 @@ class ArticlesScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
|
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
if (user.hasValue && !contentOnly)
|
if (user.value != null && !contentOnly)
|
||||||
SliverToBoxAdapter(child: CheckInWidget()),
|
SliverToBoxAdapter(child: CheckInWidget()),
|
||||||
SliverList.builder(
|
SliverList.builder(
|
||||||
itemCount: widgetCount,
|
itemCount: widgetCount,
|
||||||
@ -338,7 +338,7 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
bottom: 16,
|
bottom: 16,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
onRefresh: (_) {
|
onRefresh: () {
|
||||||
activitiesNotifier.forceRefresh();
|
activitiesNotifier.forceRefresh();
|
||||||
},
|
},
|
||||||
onUpdate: (post) {
|
onUpdate: (post) {
|
||||||
|
@ -321,8 +321,15 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
builder: (context, attachments, _) {
|
builder: (context, attachments, _) {
|
||||||
if (attachments.isEmpty) return const SizedBox.shrink();
|
if (attachments.isEmpty) return const SizedBox.shrink();
|
||||||
return Column(
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
|
Text(
|
||||||
|
'articleAttachmentHint'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
).padding(bottom: 8),
|
||||||
ValueListenableBuilder<Map<int, double>>(
|
ValueListenableBuilder<Map<int, double>>(
|
||||||
valueListenable: state.attachmentProgress,
|
valueListenable: state.attachmentProgress,
|
||||||
builder: (context, progressMap, _) {
|
builder: (context, progressMap, _) {
|
||||||
@ -332,8 +339,8 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
for (var idx = 0; idx < attachments.length; idx++)
|
for (var idx = 0; idx < attachments.length; idx++)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 120,
|
width: 280,
|
||||||
height: 120,
|
height: 280,
|
||||||
child: AttachmentPreview(
|
child: AttachmentPreview(
|
||||||
item: attachments[idx],
|
item: attachments[idx],
|
||||||
progress: progressMap[idx],
|
progress: progressMap[idx],
|
||||||
@ -358,6 +365,12 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
delta,
|
delta,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
onInsert:
|
||||||
|
() => ComposeLogic.insertAttachment(
|
||||||
|
ref,
|
||||||
|
state,
|
||||||
|
idx,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: TextField(
|
title: TextField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
|
@ -187,7 +187,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
context.push('/account/${data.name}');
|
context.push('/account/${data.account?.name}');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -77,6 +77,7 @@ class RealmDetailScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
|
noBackground: false,
|
||||||
body: realmState.when(
|
body: realmState.when(
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (error, _) => Center(child: Text('Error: $error')),
|
error: (error, _) => Center(child: Text('Error: $error')),
|
||||||
|
105
lib/screens/reports/report_detail.dart
Normal file
105
lib/screens/reports/report_detail.dart
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/abuse_report.dart';
|
||||||
|
import 'package:island/models/abuse_report_type.dart';
|
||||||
|
import 'package:island/services/abuse_report_service.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class AbuseReportDetailScreen extends ConsumerStatefulWidget {
|
||||||
|
final String reportId;
|
||||||
|
|
||||||
|
const AbuseReportDetailScreen({super.key, required this.reportId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<AbuseReportDetailScreen> createState() =>
|
||||||
|
_AbuseReportDetailScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AbuseReportDetailScreenState
|
||||||
|
extends ConsumerState<AbuseReportDetailScreen> {
|
||||||
|
Future<SnAbuseReport>? _reportFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_reportFuture = ref
|
||||||
|
.read(abuseReportServiceProvider)
|
||||||
|
.getReport(widget.reportId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: const Text('Abuse Report Details')),
|
||||||
|
body: FutureBuilder<SnAbuseReport>(
|
||||||
|
future: _reportFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
return Center(child: Text('Error: ${snapshot.error}'));
|
||||||
|
} else if (snapshot.hasData) {
|
||||||
|
final report = snapshot.data!;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildDetailRow(context, 'Report ID', report.id),
|
||||||
|
_buildDetailRow(
|
||||||
|
context,
|
||||||
|
'Resource Identifier',
|
||||||
|
report.resourceIdentifier,
|
||||||
|
),
|
||||||
|
_buildDetailRow(
|
||||||
|
context,
|
||||||
|
'Type',
|
||||||
|
AbuseReportType.fromValue(report.type).displayName,
|
||||||
|
),
|
||||||
|
_buildDetailRow(context, 'Reason', report.reason),
|
||||||
|
_buildDetailRow(
|
||||||
|
context,
|
||||||
|
'Resolved At',
|
||||||
|
report.resolvedAt?.toString() ?? 'N/A',
|
||||||
|
),
|
||||||
|
_buildDetailRow(
|
||||||
|
context,
|
||||||
|
'Resolution',
|
||||||
|
report.resolution ?? 'N/A',
|
||||||
|
),
|
||||||
|
_buildDetailRow(context, 'Account ID', report.accountId),
|
||||||
|
_buildDetailRow(
|
||||||
|
context,
|
||||||
|
'Created At',
|
||||||
|
report.createdAt.toString(),
|
||||||
|
),
|
||||||
|
_buildDetailRow(
|
||||||
|
context,
|
||||||
|
'Updated At',
|
||||||
|
report.updatedAt.toString(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const Center(child: Text('No data'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetailRow(BuildContext context, String label, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(label, style: Theme.of(context).textTheme.titleMedium).bold(),
|
||||||
|
Text(value, style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
153
lib/screens/reports/report_list.dart
Normal file
153
lib/screens/reports/report_list.dart
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/abuse_report.dart';
|
||||||
|
import 'package:island/models/abuse_report_type.dart';
|
||||||
|
import 'package:island/services/abuse_report_service.dart';
|
||||||
|
import 'package:island/services/time.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/safety/abuse_report_helper.dart';
|
||||||
|
|
||||||
|
class AbuseReportListScreen extends ConsumerStatefulWidget {
|
||||||
|
const AbuseReportListScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<AbuseReportListScreen> createState() =>
|
||||||
|
_AbuseReportListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AbuseReportListScreenState extends ConsumerState<AbuseReportListScreen> {
|
||||||
|
Future<List<SnAbuseReport>>? _reportsFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_reportsFuture = ref.read(abuseReportServiceProvider).getReports();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text('abuseReports').tr()),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
showAbuseReportSheet(context, resourceIdentifier: 'unidentified');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: FutureBuilder<List<SnAbuseReport>>(
|
||||||
|
future: _reportsFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
return Center(child: Text('Error: ${snapshot.error}'));
|
||||||
|
} else if (snapshot.hasData) {
|
||||||
|
final reports = snapshot.data!;
|
||||||
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: reports.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final report = reports[index];
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
context.push('/safety/reports/me/${report.id}');
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
report.reason,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'ID',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
report.id,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Type',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
AbuseReportType.fromValue(
|
||||||
|
report.type,
|
||||||
|
).displayName,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Created at',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${report.createdAt.formatRelative(context)} · ${report.createdAt.formatSystem()}',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Status',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
report.resolvedAt != null
|
||||||
|
? 'Resolved'
|
||||||
|
: 'Unresolved',
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodyMedium?.copyWith(
|
||||||
|
color:
|
||||||
|
report.resolvedAt != null
|
||||||
|
? Colors.green
|
||||||
|
: Colors.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const Center(child: Text('No data'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
lib/services/abuse_report_service.dart
Normal file
25
lib/services/abuse_report_service.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/abuse_report.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
|
||||||
|
final abuseReportServiceProvider = Provider<AbuseReportService>((ref) {
|
||||||
|
return AbuseReportService(ref);
|
||||||
|
});
|
||||||
|
|
||||||
|
class AbuseReportService {
|
||||||
|
final Ref ref;
|
||||||
|
AbuseReportService(this.ref);
|
||||||
|
|
||||||
|
Future<SnAbuseReport> getReport(String id) async {
|
||||||
|
final response =
|
||||||
|
await ref.read(apiClientProvider).get('/safety/reports/me/$id');
|
||||||
|
return SnAbuseReport.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SnAbuseReport>> getReports() async {
|
||||||
|
final response = await ref.read(apiClientProvider).get('/safety/reports/me');
|
||||||
|
return (response.data as List)
|
||||||
|
.map((json) => SnAbuseReport.fromJson(json))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
@ -63,9 +63,11 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> subscribePushNotification(Dio apiClient) async {
|
Future<void> subscribePushNotification(
|
||||||
|
Dio apiClient, {
|
||||||
|
bool detailedErrors = false,
|
||||||
|
}) async {
|
||||||
await FirebaseMessaging.instance.requestPermission(
|
await FirebaseMessaging.instance.requestPermission(
|
||||||
provisional: true,
|
|
||||||
alert: true,
|
alert: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
sound: true,
|
sound: true,
|
||||||
@ -97,6 +99,8 @@ Future<void> subscribePushNotification(Dio apiClient) async {
|
|||||||
deviceToken,
|
deviceToken,
|
||||||
!kIsWeb && (Platform.isIOS || Platform.isMacOS) ? 0 : 1,
|
!kIsWeb && (Platform.isIOS || Platform.isMacOS) ? 0 : 1,
|
||||||
);
|
);
|
||||||
|
} else if (detailedErrors) {
|
||||||
|
throw Exception("Failed to get device token for push notifications.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/widgets/tour/techincal_review_intro.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'tour.g.dart';
|
part 'tour.g.dart';
|
||||||
@ -12,7 +11,7 @@ part 'tour.freezed.dart';
|
|||||||
const kAppTourStatusKey = "app_tour_statuses";
|
const kAppTourStatusKey = "app_tour_statuses";
|
||||||
|
|
||||||
const List<Tour> kAllTours = [
|
const List<Tour> kAllTours = [
|
||||||
Tour(id: 'technical_review_intro', isStartup: true),
|
// Tour(id: 'technical_review_intro', isStartup: true),
|
||||||
];
|
];
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -22,7 +21,7 @@ sealed class Tour with _$Tour {
|
|||||||
const factory Tour({required String id, required bool isStartup}) = _Tour;
|
const factory Tour({required String id, required bool isStartup}) = _Tour;
|
||||||
|
|
||||||
Widget get widget => switch (id) {
|
Widget get widget => switch (id) {
|
||||||
'technical_review_intro' => const TechicalReviewIntroWidget(),
|
// 'technical_review_intro' => const TechicalReviewIntroWidget(),
|
||||||
_ => throw UnimplementedError(),
|
_ => throw UnimplementedError(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
22
lib/utils/abuse_report_utils.dart
Normal file
22
lib/utils/abuse_report_utils.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
String getAbuseReportTypeString(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
return 'Copyright';
|
||||||
|
case 1:
|
||||||
|
return 'Harassment';
|
||||||
|
case 2:
|
||||||
|
return 'Impersonation';
|
||||||
|
case 3:
|
||||||
|
return 'Offensive Content';
|
||||||
|
case 4:
|
||||||
|
return 'Spam';
|
||||||
|
case 5:
|
||||||
|
return 'Privacy Violation';
|
||||||
|
case 6:
|
||||||
|
return 'Illegal Content';
|
||||||
|
case 7:
|
||||||
|
return 'Other';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
@ -59,7 +59,7 @@ class AccountStatusCreationSheet extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
|
options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
|
||||||
);
|
);
|
||||||
if (user.hasValue) {
|
if (user.value != null) {
|
||||||
ref.invalidate(accountStatusProvider(user.value!.name));
|
ref.invalidate(accountStatusProvider(user.value!.name));
|
||||||
}
|
}
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
@ -350,7 +350,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
return AnimatedPositioned(
|
return AnimatedPositioned(
|
||||||
duration: Duration(milliseconds: 1850),
|
duration: Duration(milliseconds: 1850),
|
||||||
top:
|
top:
|
||||||
!user.hasValue ||
|
user.value == null ||
|
||||||
user.value == null ||
|
user.value == null ||
|
||||||
websocketState == WebSocketState.connected()
|
websocketState == WebSocketState.connected()
|
||||||
? -indicatorHeight
|
? -indicatorHeight
|
||||||
@ -362,7 +362,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation:
|
elevation:
|
||||||
!user.hasValue || websocketState == WebSocketState.connected()
|
user.value == null || websocketState == WebSocketState.connected()
|
||||||
? 0
|
? 0
|
||||||
: 4,
|
: 4,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
|
@ -44,7 +44,7 @@ class AudioCallButton extends HookConsumerWidget {
|
|||||||
try {
|
try {
|
||||||
await apiClient.post('/chat/realtime/$roomId');
|
await apiClient.post('/chat/realtime/$roomId');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.push('/chat/call/$roomId');
|
context.push('/chat/$roomId/call');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorAlert(e);
|
showErrorAlert(e);
|
||||||
|
@ -360,7 +360,7 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
).padding(all: 16),
|
).padding(all: 16),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.push('/chat/call/${callNotifier.roomId!}');
|
context.push('/chat/${callNotifier.roomId!}/call');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ class AttachmentPreview extends StatelessWidget {
|
|||||||
final double? progress;
|
final double? progress;
|
||||||
final Function(int)? onMove;
|
final Function(int)? onMove;
|
||||||
final Function? onDelete;
|
final Function? onDelete;
|
||||||
|
final Function? onInsert;
|
||||||
final Function? onRequestUpload;
|
final Function? onRequestUpload;
|
||||||
const AttachmentPreview({
|
const AttachmentPreview({
|
||||||
super.key,
|
super.key,
|
||||||
@ -23,13 +24,17 @@ class AttachmentPreview extends StatelessWidget {
|
|||||||
this.onRequestUpload,
|
this.onRequestUpload,
|
||||||
this.onMove,
|
this.onMove,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
|
this.onInsert,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var ratio =
|
||||||
|
(item.isOnCloud ? (item.data.fileMeta?['ratio'] ?? 1) : 1).toDouble();
|
||||||
|
if (ratio == 0) ratio = 1.0;
|
||||||
|
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio:
|
aspectRatio: ratio,
|
||||||
(item.isOnCloud ? (item.data.fileMeta?['ratio'] ?? 1) : 1).toDouble(),
|
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@ -104,7 +109,11 @@ class AttachmentPreview extends StatelessWidget {
|
|||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
Gap(6),
|
Gap(6),
|
||||||
Center(child: LinearProgressIndicator(value: progress)),
|
Center(
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: progress != null ? progress! / 100.0 : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -166,6 +175,18 @@ class AttachmentPreview extends StatelessWidget {
|
|||||||
onMove?.call(1);
|
onMove?.call(1);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (onInsert != null)
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: const Icon(
|
||||||
|
Symbols.add,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
).padding(horizontal: 8, vertical: 6),
|
||||||
|
onTap: () {
|
||||||
|
onInsert?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -37,13 +37,10 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
|
|
||||||
double calculateAspectRatio() {
|
double calculateAspectRatio() {
|
||||||
double total = 0;
|
double total = 0;
|
||||||
for (var ratio in files.map(
|
for (var ratio in files.map((e) => e.fileMeta?['ratio'] ?? 1)) {
|
||||||
(e) =>
|
|
||||||
e.fileMeta?['ratio'] ??
|
|
||||||
((e.mimeType?.startsWith('image') ?? false) ? 1 : 16 / 9),
|
|
||||||
)) {
|
|
||||||
total += ratio;
|
total += ratio;
|
||||||
}
|
}
|
||||||
|
if (total == 0) return 1;
|
||||||
return total / files.length;
|
return total / files.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,22 +25,21 @@ class CloudFileWidget extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/files/${item.id}';
|
final uri = '$serverUrl/api/files/${item.id}';
|
||||||
|
|
||||||
|
var ratio = (item.fileMeta?['ratio'] ?? 1).toDouble();
|
||||||
|
if (ratio == 0) ratio = 1.0;
|
||||||
final content = switch (item.mimeType?.split('/').firstOrNull) {
|
final content = switch (item.mimeType?.split('/').firstOrNull) {
|
||||||
"image" => AspectRatio(
|
"image" => AspectRatio(
|
||||||
aspectRatio: (item.fileMeta?['ratio'] ?? 1).toDouble(),
|
aspectRatio: ratio,
|
||||||
child: UniversalImage(
|
child: UniversalImage(
|
||||||
uri: uri,
|
uri: uri,
|
||||||
blurHash: noBlurhash ? null : item.fileMeta?['blur'],
|
blurHash: noBlurhash ? null : item.fileMeta?['blur'],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
"video" => AspectRatio(
|
"video" => AspectRatio(
|
||||||
aspectRatio: (item.fileMeta?['ratio'] ?? 16 / 9).toDouble(),
|
aspectRatio: ratio,
|
||||||
child: UniversalVideo(
|
child: UniversalVideo(uri: uri, aspectRatio: ratio),
|
||||||
uri: uri,
|
|
||||||
aspectRatio: (item.fileMeta?['ratio'] ?? 16 / 9).toDouble(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
_ => Text('Unable render for ${item.mimeType}'),
|
_ => Text('Unable render for ${item.mimeType}'),
|
||||||
};
|
};
|
||||||
@ -71,7 +70,7 @@ class CloudImageWidget extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/files/${file?.id ?? fileId}';
|
final uri = '$serverUrl/api/files/${file?.id ?? fileId}';
|
||||||
|
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio: aspectRatio,
|
aspectRatio: aspectRatio,
|
||||||
@ -87,7 +86,7 @@ class CloudImageWidget extends ConsumerWidget {
|
|||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
bool original = false,
|
bool original = false,
|
||||||
}) {
|
}) {
|
||||||
final uri = '$serverUrl/files/$fileId?original=$original';
|
final uri = '$serverUrl/api/files/$fileId?original=$original';
|
||||||
return CachedNetworkImageProvider(uri);
|
return CachedNetworkImageProvider(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +109,7 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/files/${file?.id ?? fileId}';
|
final uri = '$serverUrl/api/files/${file?.id ?? fileId}';
|
||||||
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(radius)),
|
borderRadius: BorderRadius.all(Radius.circular(radius)),
|
||||||
@ -303,7 +302,7 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/files/$fileId';
|
final uri = '$serverUrl/api/files/$fileId';
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: radius,
|
width: radius,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -6,11 +7,14 @@ import 'package:flutter_highlight/themes/a11y-dark.dart';
|
|||||||
import 'package:flutter_highlight/themes/a11y-light.dart';
|
import 'package:flutter_highlight/themes/a11y-light.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/markdown_latex.dart';
|
import 'package:island/widgets/content/markdown_latex.dart';
|
||||||
import 'package:markdown/markdown.dart' as markdown;
|
import 'package:markdown/markdown.dart' as markdown;
|
||||||
import 'package:markdown_widget/markdown_widget.dart';
|
import 'package:markdown_widget/markdown_widget.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import 'image.dart';
|
import 'image.dart';
|
||||||
@ -23,6 +27,7 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
final TextStyle? linkStyle;
|
final TextStyle? linkStyle;
|
||||||
final EdgeInsets? linesMargin;
|
final EdgeInsets? linesMargin;
|
||||||
final bool isSelectable;
|
final bool isSelectable;
|
||||||
|
final List<SnCloudFile>? attachments;
|
||||||
|
|
||||||
const MarkdownTextContent({
|
const MarkdownTextContent({
|
||||||
super.key,
|
super.key,
|
||||||
@ -33,6 +38,7 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
this.linkStyle,
|
this.linkStyle,
|
||||||
this.isSelectable = false,
|
this.isSelectable = false,
|
||||||
this.linesMargin,
|
this.linesMargin,
|
||||||
|
this.attachments,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -109,6 +115,29 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
if (uri.scheme == 'solian') {
|
if (uri.scheme == 'solian') {
|
||||||
switch (uri.host) {
|
switch (uri.host) {
|
||||||
|
case 'files':
|
||||||
|
final file = attachments?.firstWhereOrNull(
|
||||||
|
(file) => file.id == uri.pathSegments[0],
|
||||||
|
);
|
||||||
|
if (file == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: CloudFileWidget(
|
||||||
|
item: file,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
).clipRRect(all: 8),
|
||||||
|
),
|
||||||
|
);
|
||||||
case 'stickers':
|
case 'stickers':
|
||||||
final size = doesEnlargeSticker ? 96.0 : 24.0;
|
final size = doesEnlargeSticker ? 96.0 : 24.0;
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
@ -132,9 +161,9 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final content = UniversalImage(
|
final content = ConstrainedBox(
|
||||||
uri: uri.toString(),
|
constraints: BoxConstraints(maxHeight: 360),
|
||||||
fit: BoxFit.cover,
|
child: UniversalImage(uri: uri.toString(), fit: BoxFit.contain),
|
||||||
);
|
);
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
|
@ -70,7 +70,7 @@ class _UniversalVideoState extends ConsumerState<UniversalVideo> {
|
|||||||
|
|
||||||
return Video(
|
return Video(
|
||||||
controller: _videoController!,
|
controller: _videoController!,
|
||||||
aspectRatio: widget.aspectRatio,
|
aspectRatio: widget.aspectRatio != 1 ? widget.aspectRatio : null,
|
||||||
controls:
|
controls:
|
||||||
!kIsWeb && (Platform.isAndroid || Platform.isIOS)
|
!kIsWeb && (Platform.isAndroid || Platform.isIOS)
|
||||||
? MaterialVideoControls
|
? MaterialVideoControls
|
||||||
|
@ -474,6 +474,23 @@ class ComposeLogic {
|
|||||||
state.attachments.value = clone;
|
state.attachments.value = clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void insertAttachment(WidgetRef ref, ComposeState state, int index) {
|
||||||
|
final attachment = state.attachments.value[index];
|
||||||
|
if (!attachment.isOnCloud) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final cloudFile = attachment.data as SnCloudFile;
|
||||||
|
final markdown = '';
|
||||||
|
final controller = state.contentController;
|
||||||
|
final text = controller.text;
|
||||||
|
final selection = controller.selection;
|
||||||
|
final newText = text.replaceRange(selection.start, selection.end, markdown);
|
||||||
|
controller.text = newText;
|
||||||
|
controller.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: selection.start + markdown.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> performAction(
|
static Future<void> performAction(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
|
@ -56,7 +56,7 @@ class PostItem extends HookConsumerWidget {
|
|||||||
|
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
final isAuthor = useMemoized(
|
final isAuthor = useMemoized(
|
||||||
() => user.hasValue && user.value?.id == item.publisher.accountId,
|
() => user.value != null && user.value?.id == item.publisher.accountId,
|
||||||
[user],
|
[user],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ class PostItem extends HookConsumerWidget {
|
|||||||
if ((item.repliedPost != null || item.forwardedPost != null) &&
|
if ((item.repliedPost != null || item.forwardedPost != null) &&
|
||||||
showReferencePost)
|
showReferencePost)
|
||||||
_buildReferencePost(context, item),
|
_buildReferencePost(context, item),
|
||||||
if (item.attachments.isNotEmpty)
|
if (item.attachments.isNotEmpty && item.type != 1)
|
||||||
CloudFileList(
|
CloudFileList(
|
||||||
files: item.attachments,
|
files: item.attachments,
|
||||||
maxWidth: math.min(
|
maxWidth: math.min(
|
||||||
@ -331,6 +331,7 @@ class PostItem extends HookConsumerWidget {
|
|||||||
item.type == 0
|
item.type == 0
|
||||||
? EdgeInsets.only(bottom: 8)
|
? EdgeInsets.only(bottom: 8)
|
||||||
: null,
|
: null,
|
||||||
|
attachments: item.attachments,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// Render tags and categories if they exist
|
// Render tags and categories if they exist
|
||||||
@ -383,13 +384,18 @@ class PostItem extends HookConsumerWidget {
|
|||||||
// Show truncation hint if post is truncated
|
// Show truncation hint if post is truncated
|
||||||
if (item.isTruncated && !isFullPost && item.type != 1)
|
if (item.isTruncated && !isFullPost && item.type != 1)
|
||||||
_PostTruncateHint().padding(
|
_PostTruncateHint().padding(
|
||||||
bottom: item.attachments.isNotEmpty ? 8 : null,
|
bottom:
|
||||||
|
(item.attachments.isNotEmpty ||
|
||||||
|
item.repliedPost != null ||
|
||||||
|
item.forwardedPost != null)
|
||||||
|
? 8
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
if ((item.repliedPost != null ||
|
if ((item.repliedPost != null ||
|
||||||
item.forwardedPost != null) &&
|
item.forwardedPost != null) &&
|
||||||
showReferencePost)
|
showReferencePost)
|
||||||
_buildReferencePost(context, item),
|
_buildReferencePost(context, item),
|
||||||
if (item.attachments.isNotEmpty)
|
if (item.attachments.isNotEmpty && item.type != 1)
|
||||||
CloudFileList(
|
CloudFileList(
|
||||||
files: item.attachments,
|
files: item.attachments,
|
||||||
maxWidth: math.min(
|
maxWidth: math.min(
|
||||||
@ -570,7 +576,7 @@ class PostItem extends HookConsumerWidget {
|
|||||||
callback: () {
|
callback: () {
|
||||||
showAbuseReportSheet(
|
showAbuseReportSheet(
|
||||||
context,
|
context,
|
||||||
resourceIdentifier: 'posts:${item.id}',
|
resourceIdentifier: 'post/${item.id}',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -689,6 +695,7 @@ Widget _buildReferencePost(BuildContext context, SnPost item) {
|
|||||||
referencePost.type == 0
|
referencePost.type == 0
|
||||||
? EdgeInsets.only(bottom: 4)
|
? EdgeInsets.only(bottom: 4)
|
||||||
: null,
|
: null,
|
||||||
|
attachments: item.attachments,
|
||||||
).padding(bottom: 4),
|
).padding(bottom: 4),
|
||||||
// Truncation hint for referenced post
|
// Truncation hint for referenced post
|
||||||
if (referencePost.isTruncated)
|
if (referencePost.isTruncated)
|
||||||
@ -696,7 +703,8 @@ Widget _buildReferencePost(BuildContext context, SnPost item) {
|
|||||||
isCompact: true,
|
isCompact: true,
|
||||||
margin: const EdgeInsets.only(top: 4, bottom: 8),
|
margin: const EdgeInsets.only(top: 4, bottom: 8),
|
||||||
),
|
),
|
||||||
if (referencePost.attachments.isNotEmpty)
|
if (referencePost.attachments.isNotEmpty &&
|
||||||
|
referencePost.type != 1)
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -1030,6 +1038,7 @@ class _ArticlePostDisplay extends StatelessWidget {
|
|||||||
MarkdownTextContent(
|
MarkdownTextContent(
|
||||||
content: item.content!,
|
content: item.content!,
|
||||||
textStyle: Theme.of(context).textTheme.bodyLarge,
|
textStyle: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
attachments: item.attachments,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/post/post_replies.dart';
|
import 'package:island/widgets/post/post_replies.dart';
|
||||||
import 'package:island/widgets/post/post_quick_reply.dart';
|
import 'package:island/widgets/post/post_quick_reply.dart';
|
||||||
@ -14,6 +15,8 @@ class PostRepliesSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText: 'repliesCount'.plural(post.repliesCount),
|
titleText: 'repliesCount'.plural(post.repliesCount),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -21,13 +24,16 @@ class PostRepliesSheet extends HookConsumerWidget {
|
|||||||
// Replies list
|
// Replies list
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [PostRepliesList(
|
slivers: [
|
||||||
|
PostRepliesList(
|
||||||
postId: post.id.toString(),
|
postId: post.id.toString(),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
)],
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Quick reply section
|
// Quick reply section
|
||||||
|
if (user.value != null)
|
||||||
Material(
|
Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: PostQuickReply(
|
child: PostQuickReply(
|
||||||
|
@ -55,7 +55,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||||
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
|
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
|
||||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||||
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))
|
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
||||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
|
@ -16,10 +16,10 @@ PODS:
|
|||||||
- Firebase/Messaging (11.15.0):
|
- Firebase/Messaging (11.15.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.15.0)
|
- FirebaseMessaging (~> 11.15.0)
|
||||||
- firebase_core (3.15.0):
|
- firebase_core (3.15.1):
|
||||||
- Firebase/CoreOnly (~> 11.15.0)
|
- Firebase/CoreOnly (~> 11.15.0)
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_messaging (15.2.8):
|
- firebase_messaging (15.2.9):
|
||||||
- Firebase/CoreOnly (~> 11.15.0)
|
- Firebase/CoreOnly (~> 11.15.0)
|
||||||
- Firebase/Messaging (~> 11.15.0)
|
- Firebase/Messaging (~> 11.15.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
@ -292,8 +292,8 @@ SPEC CHECKSUMS:
|
|||||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||||
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||||
firebase_core: 177f51be1650b15d2d5b9f1abf48792619288070
|
firebase_core: 8dc569d17b3a9fc3ee5ebc21b322411b4a796833
|
||||||
firebase_messaging: 8748a5d4bb435993cffa7f5501292f3e914a23d7
|
firebase_messaging: adaf7fc22897a7aa49410d15f8a595bef2dbca2d
|
||||||
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
||||||
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
||||||
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
||||||
@ -310,7 +310,7 @@ SPEC CHECKSUMS:
|
|||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||||
livekit_client: c9d9f41996f5cf22b9ba0e8483e6af4ca5094059
|
livekit_client: c9d9f41996f5cf22b9ba0e8483e6af4ca5094059
|
||||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||||
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
||||||
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
48
pubspec.lock
48
pubspec.lock
@ -13,10 +13,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _flutterfire_internals
|
name: _flutterfire_internals
|
||||||
sha256: "50e24b769bd1e725732f0aff18b806b8731c1fbcf4e8018ab98e7c4805a2a52f"
|
sha256: a5788040810bd84400bc209913fbc40f388cded7cdf95ee2f5d2bff7e38d5241
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.57"
|
version: "1.3.58"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -349,10 +349,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: coverage
|
name: coverage
|
||||||
sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080
|
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.1"
|
version: "1.15.0"
|
||||||
croppy:
|
croppy:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -629,50 +629,50 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_core
|
name: firebase_core
|
||||||
sha256: "5bba5924139e91d26446fd2601c18a6aa62c1161c768a989bb5e245dcdc20644"
|
sha256: c6e8a6bf883d8ddd0dec39be90872daca65beaa6f4cff0051ed3b16c56b82e9f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.15.0"
|
version: "3.15.1"
|
||||||
firebase_core_platform_interface:
|
firebase_core_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_platform_interface
|
name: firebase_core_platform_interface
|
||||||
sha256: "5d2ab45779d91af2aa0252dec9fe4ee1caa015d83377de255454dcaa1526a0e0"
|
sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.1"
|
version: "6.0.0"
|
||||||
firebase_core_web:
|
firebase_core_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_web
|
name: firebase_core_web
|
||||||
sha256: eb3afccfc452b2b2075acbe0c4b27de62dd596802b4e5e19869c1e926cbb20b3
|
sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.24.0"
|
version: "2.24.1"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging
|
name: firebase_messaging
|
||||||
sha256: c6711cf2f455532b84a94022c7aaf85088849763af2f01b775ca79d82d10a01a
|
sha256: "0f3363f97672eb9f65609fa00ed2f62cc8ec93e7e2d4def99726f9165d3d8a73"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.8"
|
version: "15.2.9"
|
||||||
firebase_messaging_platform_interface:
|
firebase_messaging_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_platform_interface
|
name: firebase_messaging_platform_interface
|
||||||
sha256: "1c9dacccb1aee1bf17ba519dda5563a16fdd2ec1e79b5f2e421cb4bf75a166f7"
|
sha256: "7a05ef119a14c5f6a9440d1e0223bcba20c8daf555450e119c4c477bf2c3baa9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.8"
|
version: "4.6.9"
|
||||||
firebase_messaging_web:
|
firebase_messaging_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_web
|
name: firebase_messaging_web
|
||||||
sha256: "54317c26fa92f0d90a2017977ac791cb0504eca29fcf397f06adf727d4a7a2d5"
|
sha256: a4547f76da2a905190f899eb4d0150e1d0fd52206fce469d9f05ae15bb68b2c5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.8"
|
version: "3.10.9"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1049,18 +1049,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: freezed
|
name: freezed
|
||||||
sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c"
|
sha256: "2d399f823b8849663744d2a9ddcce01c49268fb4170d0442a655bf6a2f47be22"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.1.0"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: freezed_annotation
|
name: freezed_annotation
|
||||||
sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b
|
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.1.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1385,10 +1385,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: local_auth_darwin
|
name: local_auth_darwin
|
||||||
sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2"
|
sha256: "25163ce60a5a6c468cf7a0e3dc8a165f824cabc2aa9e39a5e9fc5c2311b7686f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.3"
|
version: "1.5.0"
|
||||||
local_auth_platform_interface:
|
local_auth_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2174,10 +2174,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_helper
|
name: source_helper
|
||||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
sha256: "4f81479fe5194a622cdd1713fe1ecb683a6e6c85cd8cec8e2e35ee5ab3fdf2a1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.3.6"
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 3.0.0+110
|
version: 3.1.0+113
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.2
|
sdk: ^3.7.2
|
||||||
|
25
web/.well-known/apple-app-site-association
Normal file
25
web/.well-known/apple-app-site-association
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"applinks": {
|
||||||
|
"apps": [],
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"appIDs": [
|
||||||
|
"W7HPZ53V6B.dev.solsynth.solian"
|
||||||
|
],
|
||||||
|
"paths": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"/": "/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"webcredentials": {
|
||||||
|
"apps": [
|
||||||
|
"W7HPZ53V6B.dev.solsynth.solian"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
12
web/.well-known/assetlinks.json
Normal file
12
web/.well-known/assetlinks.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "dev.solsynth.solian",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"57:0C:A4:E6:1F:57:DF:56:70:42:05:4B:43:E2:DD:9E:00:E6:77:C3:D8:3C:5F:D5:A0:05:59:30:5A:85:F9:BC"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
Reference in New Issue
Block a user