Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
3ac6822ab6 | |||
7a5fd2e468 | |||
e1ddd22e4e | |||
22b2ae32e9 | |||
9d5c452eae | |||
0fdb1e4ead | |||
724bd6592e | |||
2d347e0d41 | |||
de39799301 | |||
4b921602a2 |
@ -22,9 +22,9 @@
|
|||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
"posts": "Posts",
|
"posts": "Posts",
|
||||||
"unlink": "Unlink",
|
"unlink": "Unlink",
|
||||||
"feedSearch": "Search Feed",
|
"postSearch": "Search Post",
|
||||||
"feedSearchWithTag": "Searching with tag #@key",
|
"postSearchWithTag": "Searching with tag #@key",
|
||||||
"feedSearchWithCategory": "Searching in category @category",
|
"postSearchWithCategory": "Searching in category @category",
|
||||||
"feedUnreadCount": "@count posts you may missed",
|
"feedUnreadCount": "@count posts you may missed",
|
||||||
"messages": "Messages",
|
"messages": "Messages",
|
||||||
"messagesUnreadCount": "@count messages unread",
|
"messagesUnreadCount": "@count messages unread",
|
||||||
@ -433,6 +433,7 @@
|
|||||||
"updateCheckStrictly": "Strict mode",
|
"updateCheckStrictly": "Strict mode",
|
||||||
"updateCheckStrictlyDesc": "If enabled, the app will ask for updating once the local version is different from remote one.",
|
"updateCheckStrictlyDesc": "If enabled, the app will ask for updating once the local version is different from remote one.",
|
||||||
"updateMayAvailable": "App version @version is available, you can update from app store or our website.",
|
"updateMayAvailable": "App version @version is available, you can update from app store or our website.",
|
||||||
|
"updateNow": "Update now",
|
||||||
"termAccept": "I've read and agree to Solar Network's Terms",
|
"termAccept": "I've read and agree to Solar Network's Terms",
|
||||||
"termAcceptDesc": "Including but not limited to \"User Agreement\" and \"Privacy Policy\"",
|
"termAcceptDesc": "Including but not limited to \"User Agreement\" and \"Privacy Policy\"",
|
||||||
"termAcceptLink": "View terms",
|
"termAcceptLink": "View terms",
|
||||||
@ -452,5 +453,9 @@
|
|||||||
"accountDeletionConfirm": "Confirm request account deletion",
|
"accountDeletionConfirm": "Confirm request account deletion",
|
||||||
"accountDeletionConfirmDesc": "Are you sure to delete account @account? You will receive a confirmation email with a link to confirm the deletion of the account within 24 hours. Note that this action is irreversible, and all data associated with the account will be deleted, and you should be careful about it.",
|
"accountDeletionConfirmDesc": "Are you sure to delete account @account? You will receive a confirmation email with a link to confirm the deletion of the account within 24 hours. Note that this action is irreversible, and all data associated with the account will be deleted, and you should be careful about it.",
|
||||||
"accountDeletionRequested": "Account deletion requested, check your inbox to confirm the request.",
|
"accountDeletionRequested": "Account deletion requested, check your inbox to confirm the request.",
|
||||||
"slideToConfirm": "Slide to confirm"
|
"slideToConfirm": "Slide to confirm",
|
||||||
|
"serviceStatus": "Status of Service",
|
||||||
|
"firstBootTime": "First boot at @time",
|
||||||
|
"rateTheApp": "Rate the app",
|
||||||
|
"rateTheAppDesc": "Rate Solar Network on the App Store to let us serve you better!"
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@
|
|||||||
"dashboard": "仪表盘",
|
"dashboard": "仪表盘",
|
||||||
"today": "今日",
|
"today": "今日",
|
||||||
"yesterday": "昨日",
|
"yesterday": "昨日",
|
||||||
"feedSearch": "搜索资讯",
|
"postSearch": "搜索帖子",
|
||||||
"feedSearchWithTag": "检索带有 #@key 标签的资讯",
|
"postSearchWithTag": "检索带有 #@key 标签的资讯",
|
||||||
"feedSearchWithCategory": "检索位于分类 @category 的资讯",
|
"postSearchWithCategory": "检索位于分类 @category 的资讯",
|
||||||
"feedUnreadCount": "@count 条你可能错过的帖子",
|
"feedUnreadCount": "@count 条你可能错过的帖子",
|
||||||
"messages": "消息",
|
"messages": "消息",
|
||||||
"messagesUnreadCount": "@count 条未读的消息",
|
"messagesUnreadCount": "@count 条未读的消息",
|
||||||
@ -428,6 +428,7 @@
|
|||||||
"update": "更新",
|
"update": "更新",
|
||||||
"updateCheckStrictly": "严格模式",
|
"updateCheckStrictly": "严格模式",
|
||||||
"updateCheckStrictlyDesc": "如果启用,应用程序将会在本地版本与远程版本不同时询问更新,而不会检查版本号大小。",
|
"updateCheckStrictlyDesc": "如果启用,应用程序将会在本地版本与远程版本不同时询问更新,而不会检查版本号大小。",
|
||||||
|
"updateNow": "立即更新",
|
||||||
"updateMayAvailable": "版本 @version 现已可用,你可以前往应用商店或是我们的官网下载更新。",
|
"updateMayAvailable": "版本 @version 现已可用,你可以前往应用商店或是我们的官网下载更新。",
|
||||||
"termAccept": "我已阅读并同意 Solar Network 各项条款",
|
"termAccept": "我已阅读并同意 Solar Network 各项条款",
|
||||||
"termAcceptDesc": "包括但不限于《用户守则》和《隐私政策》",
|
"termAcceptDesc": "包括但不限于《用户守则》和《隐私政策》",
|
||||||
@ -448,5 +449,9 @@
|
|||||||
"accountDeletionConfirm": "确认账号删除请求",
|
"accountDeletionConfirm": "确认账号删除请求",
|
||||||
"accountDeletionConfirmDesc": "你确定要删除账号 @account 吗?你将会在其绑定的主要邮件地址收到一封包含着确认删除账号连接的邮件,在二十四小时内使用该连接即可完成删除账号。注意,本操作不可撤销,并且账号创建或关联的所有数据都将被删除,请三思而后行。",
|
"accountDeletionConfirmDesc": "你确定要删除账号 @account 吗?你将会在其绑定的主要邮件地址收到一封包含着确认删除账号连接的邮件,在二十四小时内使用该连接即可完成删除账号。注意,本操作不可撤销,并且账号创建或关联的所有数据都将被删除,请三思而后行。",
|
||||||
"accountDeletionRequested": "已请求删除账号,检查你的收件箱来确认请求。",
|
"accountDeletionRequested": "已请求删除账号,检查你的收件箱来确认请求。",
|
||||||
"slideToConfirm": "滑动来确认"
|
"slideToConfirm": "滑动来确认",
|
||||||
|
"serviceStatus": "服务状态",
|
||||||
|
"firstBootTime": "首次启动于 @time",
|
||||||
|
"rateTheApp": "给应用评分",
|
||||||
|
"rateTheAppDesc": "在 App Store 上给 Solar Network 评分,让我们更好地为您服务吧!"
|
||||||
}
|
}
|
||||||
|
@ -227,6 +227,8 @@ PODS:
|
|||||||
- TOCropViewController (~> 2.7.4)
|
- TOCropViewController (~> 2.7.4)
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- in_app_review (0.2.0):
|
||||||
|
- Flutter
|
||||||
- livekit_client (2.2.6):
|
- livekit_client (2.2.6):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WebRTC-SDK (= 125.6422.04)
|
- WebRTC-SDK (= 125.6422.04)
|
||||||
@ -318,6 +320,7 @@ DEPENDENCIES:
|
|||||||
- gal (from `.symlinks/plugins/gal/darwin`)
|
- gal (from `.symlinks/plugins/gal/darwin`)
|
||||||
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||||
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
@ -406,6 +409,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/image_cropper/ios"
|
:path: ".symlinks/plugins/image_cropper/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
in_app_review:
|
||||||
|
:path: ".symlinks/plugins/in_app_review/ios"
|
||||||
livekit_client:
|
livekit_client:
|
||||||
:path: ".symlinks/plugins/livekit_client/ios"
|
:path: ".symlinks/plugins/livekit_client/ios"
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
@ -482,6 +487,7 @@ SPEC CHECKSUMS:
|
|||||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||||
image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf
|
image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf
|
||||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
|
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
||||||
livekit_client: 20e01637431bc108dad451c8a11c1d206e1dd2cd
|
livekit_client: 20e01637431bc108dad451c8a11c1d206e1dd2cd
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!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>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@ -81,7 +81,12 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleLocalizations</key>
|
||||||
|
<array>
|
||||||
|
<string>zh_CN</string>
|
||||||
|
<string>en</string>
|
||||||
|
</array>
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<false/>
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@ -42,6 +44,49 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
|
|
||||||
final Completer _bootCompleter = Completer();
|
final Completer _bootCompleter = Completer();
|
||||||
|
|
||||||
|
void _requestRating() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
if (prefs.containsKey('first_boot_time')) {
|
||||||
|
final rawTime = prefs.getString('first_boot_time');
|
||||||
|
final time = DateTime.tryParse(rawTime ?? '');
|
||||||
|
if (time != null &&
|
||||||
|
time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
||||||
|
final inAppReview = InAppReview.instance;
|
||||||
|
if (prefs.getBool('rating_requested') == true) return;
|
||||||
|
if (await inAppReview.isAvailable()) {
|
||||||
|
await inAppReview.requestReview();
|
||||||
|
prefs.setBool('rating_requested', true);
|
||||||
|
} else {
|
||||||
|
log('Unable request app review, unavailable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefs.setString('first_boot_time', DateTime.now().toIso8601String());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateNow(String localVersionString, String remoteVersionString) {
|
||||||
|
context
|
||||||
|
.showConfirmDialog(
|
||||||
|
'updateAvailable'.tr,
|
||||||
|
'updateAvailableDesc'.trParams({
|
||||||
|
'from': localVersionString,
|
||||||
|
'to': remoteVersionString,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then((result) {
|
||||||
|
if (result) {
|
||||||
|
final model = UpdateModel(
|
||||||
|
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk',
|
||||||
|
'solian-app-arm64-v8a-release.apk',
|
||||||
|
'ic_launcher',
|
||||||
|
'https://testflight.apple.com/join/YJ0lmN6O',
|
||||||
|
);
|
||||||
|
AzhonAppUpdate.update(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _checkForUpdate() async {
|
Future<void> _checkForUpdate() async {
|
||||||
if (PlatformInfo.isWeb) return;
|
if (PlatformInfo.isWeb) return;
|
||||||
try {
|
try {
|
||||||
@ -70,25 +115,7 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
remoteBuildNumber > localBuildNumber) ||
|
remoteBuildNumber > localBuildNumber) ||
|
||||||
(remoteVersionString != localVersionString && strictUpdate)) {
|
(remoteVersionString != localVersionString && strictUpdate)) {
|
||||||
if (PlatformInfo.isAndroid) {
|
if (PlatformInfo.isAndroid) {
|
||||||
context
|
_updateNow(localVersionString, remoteVersionString);
|
||||||
.showConfirmDialog(
|
|
||||||
'updateAvailable'.tr,
|
|
||||||
'updateAvailableDesc'.trParams({
|
|
||||||
'from': localVersionString,
|
|
||||||
'to': remoteVersionString,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then((result) {
|
|
||||||
if (result) {
|
|
||||||
final model = UpdateModel(
|
|
||||||
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk',
|
|
||||||
'solian-app-arm64-v8a-release.apk',
|
|
||||||
'ic_launcher',
|
|
||||||
'https://testflight.apple.com/join/YJ0lmN6O',
|
|
||||||
);
|
|
||||||
AzhonAppUpdate.update(model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
context.showInfoDialog(
|
context.showInfoDialog(
|
||||||
'updateAvailable'.tr,
|
'updateAvailable'.tr,
|
||||||
@ -97,9 +124,19 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
}
|
}
|
||||||
} else if (remoteVersionString != localVersionString) {
|
} else if (remoteVersionString != localVersionString) {
|
||||||
_bootCompleter.future.then((_) {
|
_bootCompleter.future.then((_) {
|
||||||
context.showSnackbar('updateMayAvailable'.trParams({
|
context.showSnackbar(
|
||||||
|
'updateMayAvailable'.trParams({
|
||||||
'version': remoteVersionString,
|
'version': remoteVersionString,
|
||||||
}));
|
}),
|
||||||
|
action: PlatformInfo.isAndroid
|
||||||
|
? SnackBarAction(
|
||||||
|
label: 'updateNow'.tr,
|
||||||
|
onPressed: () {
|
||||||
|
_updateNow(localVersionString, remoteVersionString);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -212,6 +249,9 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_runPeriods();
|
_runPeriods();
|
||||||
_checkForUpdate();
|
_checkForUpdate();
|
||||||
|
_bootCompleter.future.then((_) {
|
||||||
|
_requestRating();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -27,6 +27,8 @@ abstract class PlatformInfo {
|
|||||||
|
|
||||||
static bool get canCacheImage => isAndroid || isIOS || isMacOS;
|
static bool get canCacheImage => isAndroid || isIOS || isMacOS;
|
||||||
|
|
||||||
|
static bool get canRateTheApp => isIOS || isMacOS;
|
||||||
|
|
||||||
static bool get canRecord => (isMobile || isMacOS);
|
static bool get canRecord => (isMobile || isMacOS);
|
||||||
|
|
||||||
static bool get canPushNotification => isAndroid || isIOS || isMacOS;
|
static bool get canPushNotification => isAndroid || isIOS || isMacOS;
|
||||||
|
@ -23,7 +23,7 @@ import 'package:solian/screens/realms.dart';
|
|||||||
import 'package:solian/screens/realms/realm_detail.dart';
|
import 'package:solian/screens/realms/realm_detail.dart';
|
||||||
import 'package:solian/screens/realms/realm_organize.dart';
|
import 'package:solian/screens/realms/realm_organize.dart';
|
||||||
import 'package:solian/screens/realms/realm_view.dart';
|
import 'package:solian/screens/realms/realm_view.dart';
|
||||||
import 'package:solian/screens/feed.dart';
|
import 'package:solian/screens/explore.dart';
|
||||||
import 'package:solian/screens/posts/post_editor.dart';
|
import 'package:solian/screens/posts/post_editor.dart';
|
||||||
import 'package:solian/screens/settings.dart';
|
import 'package:solian/screens/settings.dart';
|
||||||
import 'package:solian/shells/root_shell.dart';
|
import 'package:solian/shells/root_shell.dart';
|
||||||
@ -78,13 +78,18 @@ abstract class AppRouter {
|
|||||||
builder: (context, state, child) => child,
|
builder: (context, state, child) => child,
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/feed',
|
path: '/explore',
|
||||||
name: 'feed',
|
name: 'explore',
|
||||||
builder: (context, state) => const FeedScreen(),
|
builder: (context, state) => const ExploreScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/feed/search',
|
path: '/drafts',
|
||||||
name: 'feedSearch',
|
name: 'draftBox',
|
||||||
|
builder: (context, state) => const DraftBoxScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/posts/search',
|
||||||
|
name: 'postSearch',
|
||||||
builder: (context, state) => TitleShell(
|
builder: (context, state) => TitleShell(
|
||||||
state: state,
|
state: state,
|
||||||
child: FeedSearchScreen(
|
child: FeedSearchScreen(
|
||||||
@ -93,11 +98,6 @@ abstract class AppRouter {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/drafts',
|
|
||||||
name: 'draftBox',
|
|
||||||
builder: (context, state) => const DraftBoxScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/posts/view/:id',
|
path: '/posts/view/:id',
|
||||||
name: 'postDetail',
|
name: 'postDetail',
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:solian/widgets/sized_container.dart';
|
import 'package:solian/widgets/sized_container.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
@ -52,8 +54,9 @@ class AboutScreen extends StatelessWidget {
|
|||||||
CenteredContainer(
|
CenteredContainer(
|
||||||
maxWidth: 280,
|
maxWidth: 280,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: 8,
|
spacing: 4,
|
||||||
runSpacing: 8,
|
runSpacing: 4,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
style: denseButtonStyle,
|
style: denseButtonStyle,
|
||||||
@ -91,6 +94,13 @@ class AboutScreen extends StatelessWidget {
|
|||||||
launchUrlString('https://solsynth.dev/terms');
|
launchUrlString('https://solsynth.dev/terms');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
TextButton(
|
||||||
|
style: denseButtonStyle,
|
||||||
|
child: Text('serviceStatus'.tr),
|
||||||
|
onPressed: () {
|
||||||
|
launchUrlString('https://status.solsynth.dev');
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -102,6 +112,34 @@ class AboutScreen extends StatelessWidget {
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: SharedPreferences.getInstance(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
const textStyle = TextStyle(
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
fontSize: 12,
|
||||||
|
);
|
||||||
|
if (!snapshot.hasData ||
|
||||||
|
!snapshot.data!.containsKey('first_boot_time')) {
|
||||||
|
return Text(
|
||||||
|
'firstBootTime'.trParams({'time': 'unknown'.tr}),
|
||||||
|
style: textStyle,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text(
|
||||||
|
'firstBootTime'.trParams({
|
||||||
|
'time': DateFormat('yyyy-MM-dd').format(
|
||||||
|
DateTime.tryParse(
|
||||||
|
snapshot.data!.getString('first_boot_time')!,
|
||||||
|
)?.toLocal() ??
|
||||||
|
DateTime.now(),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
style: textStyle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -193,7 +193,9 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||||
leadingWidth: 24,
|
leadingWidth: 24,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
flexibleSpace: Row(
|
flexibleSpace: SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
AppBarLeadingButton.adaptive(context) ?? const Gap(8),
|
AppBarLeadingButton.adaptive(context) ?? const Gap(8),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -283,6 +285,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
).paddingOnly(top: MediaQuery.of(context).padding.top),
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: 'profilePage'.tr),
|
Tab(text: 'profilePage'.tr),
|
||||||
@ -296,10 +299,11 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
children: [
|
children: [
|
||||||
Column(
|
ListView(
|
||||||
children: [
|
children: [
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
AccountHeadingWidget(
|
CenteredContainer(
|
||||||
|
child: AccountHeadingWidget(
|
||||||
name: _userinfo!.name,
|
name: _userinfo!.name,
|
||||||
nick: _userinfo!.nick,
|
nick: _userinfo!.nick,
|
||||||
desc: _userinfo!.description,
|
desc: _userinfo!.description,
|
||||||
@ -315,7 +319,8 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
Card(
|
Card(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 180,
|
height: 180,
|
||||||
width: max(640, MediaQuery.of(context).size.width),
|
width:
|
||||||
|
max(640, MediaQuery.of(context).size.width),
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
lineBarsData: [
|
lineBarsData: [
|
||||||
@ -415,10 +420,12 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
borderData: FlBorderData(show: false),
|
borderData: FlBorderData(show: false),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).marginOnly(right: 24, left: 12, bottom: 8, top: 24),
|
).marginOnly(
|
||||||
|
right: 24, left: 12, bottom: 8, top: 24),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
RefreshIndicator(
|
RefreshIndicator(
|
||||||
|
@ -16,14 +16,14 @@ import 'package:solian/widgets/app_bar_leading.dart';
|
|||||||
import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
|
import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
|
||||||
import 'package:solian/widgets/posts/post_warped_list.dart';
|
import 'package:solian/widgets/posts/post_warped_list.dart';
|
||||||
|
|
||||||
class FeedScreen extends StatefulWidget {
|
class ExploreScreen extends StatefulWidget {
|
||||||
const FeedScreen({super.key});
|
const ExploreScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FeedScreen> createState() => _FeedScreenState();
|
State<ExploreScreen> createState() => _ExploreScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FeedScreenState extends State<FeedScreen>
|
class _ExploreScreenState extends State<ExploreScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final PostListController _postController;
|
late final PostListController _postController;
|
||||||
late final TabController _tabController;
|
late final TabController _tabController;
|
||||||
@ -82,7 +82,7 @@ class _FeedScreenState extends State<FeedScreen>
|
|||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
return [
|
return [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
title: AppBarTitle('feed'.tr),
|
title: AppBarTitle('explore'.tr),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
floating: true,
|
floating: true,
|
||||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
toolbarHeight: AppTheme.toolbarHeight(context),
|
@ -63,13 +63,13 @@ class _FeedSearchScreenState extends State<FeedSearchScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.label),
|
leading: const Icon(Icons.label),
|
||||||
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
title: Text('feedSearchWithTag'.trParams({'key': widget.tag!})),
|
title: Text('postSearchWithTag'.trParams({'key': widget.tag!})),
|
||||||
),
|
),
|
||||||
if (widget.category != null)
|
if (widget.category != null)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.category),
|
leading: const Icon(Icons.category),
|
||||||
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
title: Text('feedSearchWithCategory'
|
title: Text('postSearchWithCategory'
|
||||||
.trParams({'key': widget.category!})),
|
.trParams({'key': widget.category!})),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:solian/exceptions/request.dart';
|
import 'package:solian/exceptions/request.dart';
|
||||||
@ -205,6 +206,21 @@ class _SettingScreenState extends State<SettingScreen> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (PlatformInfo.canRateTheApp)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.star),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
|
||||||
|
title: Text('rateTheApp'.tr),
|
||||||
|
subtitle: Text('rateTheAppDesc'.tr),
|
||||||
|
onTap: () {
|
||||||
|
final inAppReview = InAppReview.instance;
|
||||||
|
|
||||||
|
inAppReview.openStoreListing(
|
||||||
|
appStoreId: '6499032345',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.info_outline),
|
leading: const Icon(Icons.info_outline),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
@ -21,6 +21,7 @@ class AttachmentItem extends StatefulWidget {
|
|||||||
final bool showBadge;
|
final bool showBadge;
|
||||||
final bool showHideButton;
|
final bool showHideButton;
|
||||||
final bool autoload;
|
final bool autoload;
|
||||||
|
final bool isDense;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
final String? badge;
|
final String? badge;
|
||||||
final Function? onHide;
|
final Function? onHide;
|
||||||
@ -34,6 +35,7 @@ class AttachmentItem extends StatefulWidget {
|
|||||||
this.showBadge = true,
|
this.showBadge = true,
|
||||||
this.showHideButton = true,
|
this.showHideButton = true,
|
||||||
this.autoload = false,
|
this.autoload = false,
|
||||||
|
this.isDense = false,
|
||||||
this.onHide,
|
this.onHide,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,6 +55,7 @@ class _AttachmentItemState extends State<AttachmentItem> {
|
|||||||
fit: widget.fit,
|
fit: widget.fit,
|
||||||
showBadge: widget.showBadge,
|
showBadge: widget.showBadge,
|
||||||
showHideButton: widget.showHideButton,
|
showHideButton: widget.showHideButton,
|
||||||
|
isDense: widget.isDense,
|
||||||
onHide: widget.onHide,
|
onHide: widget.onHide,
|
||||||
);
|
);
|
||||||
case 'video':
|
case 'video':
|
||||||
@ -120,6 +123,7 @@ class _AttachmentItemImage extends StatelessWidget {
|
|||||||
final bool showBadge;
|
final bool showBadge;
|
||||||
final bool showHideButton;
|
final bool showHideButton;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
|
final bool isDense;
|
||||||
final String? badge;
|
final String? badge;
|
||||||
final Function? onHide;
|
final Function? onHide;
|
||||||
|
|
||||||
@ -128,6 +132,7 @@ class _AttachmentItemImage extends StatelessWidget {
|
|||||||
required this.item,
|
required this.item,
|
||||||
required this.showBadge,
|
required this.showBadge,
|
||||||
required this.showHideButton,
|
required this.showHideButton,
|
||||||
|
required this.isDense,
|
||||||
required this.fit,
|
required this.fit,
|
||||||
this.badge,
|
this.badge,
|
||||||
this.onHide,
|
this.onHide,
|
||||||
@ -146,6 +151,7 @@ class _AttachmentItemImage extends StatelessWidget {
|
|||||||
'/attachments/${item.rid}',
|
'/attachments/${item.rid}',
|
||||||
),
|
),
|
||||||
fit: fit,
|
fit: fit,
|
||||||
|
isDense: isDense,
|
||||||
),
|
),
|
||||||
if (showBadge && badge != null)
|
if (showBadge && badge != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
|
@ -338,6 +338,7 @@ class AttachmentListEntry extends StatelessWidget {
|
|||||||
badge: showBadge ? badgeContent : null,
|
badge: showBadge ? badgeContent : null,
|
||||||
showHideButton: !item!.isMature || showMature,
|
showHideButton: !item!.isMature || showMature,
|
||||||
autoload: autoload,
|
autoload: autoload,
|
||||||
|
isDense: isDense,
|
||||||
onHide: () {
|
onHide: () {
|
||||||
onReveal(false);
|
onReveal(false);
|
||||||
},
|
},
|
||||||
|
@ -34,8 +34,17 @@ class AutoCacheImage extends StatelessWidget {
|
|||||||
progressIndicatorBuilder: noProgressIndicator
|
progressIndicatorBuilder: noProgressIndicator
|
||||||
? null
|
? null
|
||||||
: (context, url, downloadProgress) => Center(
|
: (context, url, downloadProgress) => Center(
|
||||||
child: CircularProgressIndicator(
|
child: TweenAnimationBuilder(
|
||||||
value: downloadProgress.progress,
|
tween: Tween(
|
||||||
|
begin: 0,
|
||||||
|
end: downloadProgress.progress ?? 0,
|
||||||
|
),
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
builder: (context, value, _) => CircularProgressIndicator(
|
||||||
|
value: downloadProgress.progress != null
|
||||||
|
? value.toDouble()
|
||||||
|
: null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
errorWidget: noErrorWidget
|
errorWidget: noErrorWidget
|
||||||
@ -74,12 +83,21 @@ class AutoCacheImage extends StatelessWidget {
|
|||||||
ImageChunkEvent? loadingProgress) {
|
ImageChunkEvent? loadingProgress) {
|
||||||
if (loadingProgress == null) return child;
|
if (loadingProgress == null) return child;
|
||||||
return Center(
|
return Center(
|
||||||
child: CircularProgressIndicator(
|
child: TweenAnimationBuilder(
|
||||||
value: loadingProgress.expectedTotalBytes != null
|
tween: Tween(
|
||||||
|
begin: 0,
|
||||||
|
end: loadingProgress.expectedTotalBytes != null
|
||||||
? loadingProgress.cumulativeBytesLoaded /
|
? loadingProgress.cumulativeBytesLoaded /
|
||||||
loadingProgress.expectedTotalBytes!
|
loadingProgress.expectedTotalBytes!
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
builder: (context, value, _) => CircularProgressIndicator(
|
||||||
|
value: loadingProgress.expectedTotalBytes != null
|
||||||
|
? value.toDouble()
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
errorBuilder: noErrorWidget
|
errorBuilder: noErrorWidget
|
||||||
|
@ -49,6 +49,7 @@ class ChatEventMessage extends StatelessWidget {
|
|||||||
return MarkdownTextContent(
|
return MarkdownTextContent(
|
||||||
parentId: 'm${item.id}',
|
parentId: 'm${item.id}',
|
||||||
isSelectable: true,
|
isSelectable: true,
|
||||||
|
isAutoWarp: true,
|
||||||
content: body.text,
|
content: body.text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,10 @@ class DailySignHistoryChartDialog extends StatelessWidget {
|
|||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Column(
|
: SizedBox(
|
||||||
mainAxisSize: MainAxisSize.min,
|
width: double.maxFinite,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'dailySignHistoryRecent'.tr,
|
'dailySignHistoryRecent'.tr,
|
||||||
@ -204,13 +205,15 @@ class DailySignHistoryChartDialog extends StatelessWidget {
|
|||||||
.map((spot) => LineTooltipItem(
|
.map((spot) => LineTooltipItem(
|
||||||
'+${spot.y.toStringAsFixed(0)} EXP\n${DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(spot.x.toInt()))}',
|
'+${spot.y.toStringAsFixed(0)} EXP\n${DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(spot.x.toInt()))}',
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color:
|
color: Theme.of(context)
|
||||||
Theme.of(context).colorScheme.onSurface,
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
getTooltipColor: (_) =>
|
getTooltipColor: (_) => Theme.of(context)
|
||||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
.colorScheme
|
||||||
|
.surfaceContainerHigh,
|
||||||
)),
|
)),
|
||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
topTitles: const AxisTitles(
|
topTitles: const AxisTitles(
|
||||||
@ -255,6 +258,7 @@ class DailySignHistoryChartDialog extends StatelessWidget {
|
|||||||
).marginOnly(right: 24, bottom: 8, top: 8),
|
).marginOnly(right: 24, bottom: 8, top: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown_selectionarea/flutter_markdown.dart';
|
import 'package:flutter_markdown_selectionarea/flutter_markdown.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:markdown/markdown.dart' as markdown;
|
import 'package:markdown/markdown.dart' as markdown;
|
||||||
import 'package:markdown/markdown.dart';
|
import 'package:markdown/markdown.dart';
|
||||||
@ -15,6 +16,7 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
final String parentId;
|
final String parentId;
|
||||||
final bool isSelectable;
|
final bool isSelectable;
|
||||||
final bool isLargeText;
|
final bool isLargeText;
|
||||||
|
final bool isAutoWarp;
|
||||||
|
|
||||||
const MarkdownTextContent({
|
const MarkdownTextContent({
|
||||||
super.key,
|
super.key,
|
||||||
@ -22,17 +24,36 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
required this.parentId,
|
required this.parentId,
|
||||||
this.isSelectable = false,
|
this.isSelectable = false,
|
||||||
this.isLargeText = false,
|
this.isLargeText = false,
|
||||||
|
this.isAutoWarp = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context) {
|
Widget _buildContent(BuildContext context) {
|
||||||
final emojiRegex = RegExp(r':([-\w]+):');
|
final stickerRegex = RegExp(r':([-\w]+):');
|
||||||
final emojiMatch = emojiRegex.allMatches(content);
|
|
||||||
final isOnlyEmoji = content.replaceAll(emojiRegex, '').trim().isEmpty;
|
|
||||||
|
|
||||||
return Markdown(
|
// Split the content into paragraphs
|
||||||
|
final paragraphs = content.split(RegExp(r'\n\s*\n'));
|
||||||
|
|
||||||
|
// Iterate over each paragraph to process stickers individually
|
||||||
|
List<Widget> contentWidgets = [];
|
||||||
|
for (var idx = 0; idx < paragraphs.length; idx++) {
|
||||||
|
// Getting paragraph
|
||||||
|
var paragraph = paragraphs[idx];
|
||||||
|
|
||||||
|
// Auto adding new-lines
|
||||||
|
if (isAutoWarp) {
|
||||||
|
paragraph = paragraph.replaceAll('\n', '\\\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matching stickers
|
||||||
|
final stickerMatch = stickerRegex.allMatches(paragraph);
|
||||||
|
final isOnlySticker =
|
||||||
|
paragraph.replaceAll(stickerRegex, '').trim().isEmpty;
|
||||||
|
|
||||||
|
contentWidgets.add(
|
||||||
|
Markdown(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
data: content,
|
data: paragraph,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
styleSheet: MarkdownStyleSheet.fromTheme(
|
styleSheet: MarkdownStyleSheet.fromTheme(
|
||||||
Theme.of(context),
|
Theme.of(context),
|
||||||
@ -99,10 +120,12 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
case 'stickers':
|
case 'stickers':
|
||||||
double radius = 8;
|
double radius = 8;
|
||||||
final StickerProvider sticker = Get.find();
|
final StickerProvider sticker = Get.find();
|
||||||
if (emojiMatch.length <= 1 && isOnlyEmoji) {
|
|
||||||
|
// Adjust sticker size based on the sticker count in this paragraph
|
||||||
|
if (stickerMatch.length <= 1 && isOnlySticker) {
|
||||||
width = 128;
|
width = 128;
|
||||||
height = 128;
|
height = 128;
|
||||||
} else if (emojiMatch.length <= 3 && isOnlyEmoji) {
|
} else if (stickerMatch.length <= 3 && isOnlySticker) {
|
||||||
width = 32;
|
width = 32;
|
||||||
height = 32;
|
height = 32;
|
||||||
} else {
|
} else {
|
||||||
@ -114,12 +137,15 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(radius)),
|
borderRadius: BorderRadius.all(Radius.circular(radius)),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: sticker.getStickerByAlias(segments[1]),
|
future: sticker.getStickerByAlias(segments[1]),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(
|
||||||
|
child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
return AutoCacheImage(
|
return AutoCacheImage(
|
||||||
snapshot.data!.imageUrl,
|
snapshot.data!.imageUrl,
|
||||||
@ -147,7 +173,6 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
).paddingSymmetric(vertical: 4);
|
).paddingSymmetric(vertical: 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AutoCacheImage(
|
return AutoCacheImage(
|
||||||
url,
|
url,
|
||||||
width: width,
|
width: width,
|
||||||
@ -155,6 +180,19 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
fit: fit,
|
fit: fit,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (idx < paragraphs.length - 1) {
|
||||||
|
contentWidgets.add(const Gap(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the list of widgets for the paragraphs
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: contentWidgets,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ abstract class AppNavigation {
|
|||||||
page: 'dashboard',
|
page: 'dashboard',
|
||||||
),
|
),
|
||||||
AppNavigationDestination(
|
AppNavigationDestination(
|
||||||
icon: Icons.newspaper,
|
icon: Icons.explore,
|
||||||
label: 'feed'.tr,
|
label: 'explore'.tr,
|
||||||
page: 'feed',
|
page: 'explore',
|
||||||
),
|
),
|
||||||
AppNavigationDestination(
|
AppNavigationDestination(
|
||||||
icon: Icons.workspaces,
|
icon: Icons.workspaces,
|
||||||
|
@ -18,6 +18,7 @@ import 'package:solian/widgets/link_expansion.dart';
|
|||||||
import 'package:solian/widgets/markdown_text_content.dart';
|
import 'package:solian/widgets/markdown_text_content.dart';
|
||||||
import 'package:solian/widgets/posts/post_tags.dart';
|
import 'package:solian/widgets/posts/post_tags.dart';
|
||||||
import 'package:solian/widgets/posts/post_quick_action.dart';
|
import 'package:solian/widgets/posts/post_quick_action.dart';
|
||||||
|
import 'package:solian/widgets/relative_date.dart';
|
||||||
import 'package:solian/widgets/sized_container.dart';
|
import 'package:solian/widgets/sized_container.dart';
|
||||||
import 'package:timeago/timeago.dart' show format;
|
import 'package:timeago/timeago.dart' show format;
|
||||||
|
|
||||||
@ -69,360 +70,6 @@ class _PostItemState extends State<PostItem> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDate() {
|
|
||||||
if (widget.isFullDate) {
|
|
||||||
return Text(DateFormat('y/M/d HH:mm')
|
|
||||||
.format(item.publishedAt?.toLocal() ?? DateTime.now()));
|
|
||||||
} else {
|
|
||||||
return Text(
|
|
||||||
format(
|
|
||||||
item.publishedAt?.toLocal() ?? DateTime.now(),
|
|
||||||
locale: 'en_short',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildThumbnail() {
|
|
||||||
if (widget.item.body['thumbnail'] == null) return const SizedBox.shrink();
|
|
||||||
final border = BorderSide(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 0.3,
|
|
||||||
);
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(border: Border(top: border, bottom: border)),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 16 / 9,
|
|
||||||
child: AttachmentSelfContainedEntry(
|
|
||||||
rid: widget.item.body['thumbnail'],
|
|
||||||
parentId: 'p${item.id}-thumbnail',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHeader() {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (widget.isCompact)
|
|
||||||
AccountAvatar(
|
|
||||||
content: item.author.avatar,
|
|
||||||
radius: 10,
|
|
||||||
).paddingOnly(left: 2, top: 1),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
item.author.nick,
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
_buildDate().paddingOnly(left: 4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (item.body['title'] != null)
|
|
||||||
Text(
|
|
||||||
item.body['title'],
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(fontSize: 15),
|
|
||||||
),
|
|
||||||
if (item.body['description'] != null)
|
|
||||||
Text(
|
|
||||||
item.body['description'],
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).paddingOnly(left: widget.isCompact ? 6 : 12),
|
|
||||||
),
|
|
||||||
if (widget.item.type == 'article')
|
|
||||||
Badge(
|
|
||||||
label: Text('article'.tr),
|
|
||||||
).paddingOnly(top: 3),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHeaderDivider() {
|
|
||||||
if (item.body['description'] != null || item.body['title'] != null) {
|
|
||||||
return const Divider(thickness: 0.3, height: 1).paddingSymmetric(
|
|
||||||
vertical: 8,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFooter() {
|
|
||||||
List<String> labels = List.empty(growable: true);
|
|
||||||
if (widget.item.editedAt != null) {
|
|
||||||
labels.add('postEdited'.trParams({
|
|
||||||
'date': DateFormat('yy/M/d HH:mm').format(item.editedAt!.toLocal()),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (widget.item.realm != null) {
|
|
||||||
labels.add('postInRealm'.trParams({
|
|
||||||
'realm': widget.item.realm!.alias,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> widgets = List.empty(growable: true);
|
|
||||||
|
|
||||||
if (widget.item.tags?.isNotEmpty ?? false) {
|
|
||||||
widgets.add(PostTagsList(tags: widget.item.tags!));
|
|
||||||
}
|
|
||||||
if (labels.isNotEmpty) {
|
|
||||||
widgets.add(Text(
|
|
||||||
labels.join(' · '),
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: _unFocusColor,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (widget.item.pinnedAt != null) {
|
|
||||||
widgets.add(Text(
|
|
||||||
'postPinned'.tr,
|
|
||||||
style: TextStyle(fontSize: 12, color: _unFocusColor),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widgets.isEmpty) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
} else {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: widgets,
|
|
||||||
).paddingOnly(top: 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildReply(BuildContext context) {
|
|
||||||
return OpenContainer(
|
|
||||||
tappable: widget.isClickable || widget.isOverrideEmbedClickable,
|
|
||||||
closedBuilder: (_, openContainer) => Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
FaIcon(
|
|
||||||
FontAwesomeIcons.reply,
|
|
||||||
size: 16,
|
|
||||||
color: _unFocusColor,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'postRepliedNotify'.trParams(
|
|
||||||
{'username': '@${widget.item.replyTo!.author.name}'},
|
|
||||||
),
|
|
||||||
style: TextStyle(color: _unFocusColor),
|
|
||||||
).paddingOnly(left: 6),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).paddingOnly(left: 12),
|
|
||||||
Card(
|
|
||||||
elevation: 1,
|
|
||||||
child: PostItem(
|
|
||||||
item: widget.item.replyTo!,
|
|
||||||
isCompact: true,
|
|
||||||
attachmentParent: widget.item.id.toString(),
|
|
||||||
).paddingSymmetric(vertical: 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
openBuilder: (_, __) => TitleShell(
|
|
||||||
title: 'postDetail'.tr,
|
|
||||||
child: PostDetailScreen(
|
|
||||||
id: widget.item.replyTo!.id.toString(),
|
|
||||||
post: widget.item.replyTo!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
closedElevation: 0,
|
|
||||||
openElevation: 0,
|
|
||||||
closedColor:
|
|
||||||
widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
|
||||||
openColor: Theme.of(context).colorScheme.surface,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRepost(BuildContext context) {
|
|
||||||
return OpenContainer(
|
|
||||||
tappable: widget.isClickable || widget.isOverrideEmbedClickable,
|
|
||||||
closedBuilder: (_, openContainer) => Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
FaIcon(
|
|
||||||
FontAwesomeIcons.retweet,
|
|
||||||
size: 16,
|
|
||||||
color: _unFocusColor,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'postRepostedNotify'.trParams(
|
|
||||||
{'username': '@${widget.item.repostTo!.author.name}'},
|
|
||||||
),
|
|
||||||
style: TextStyle(color: _unFocusColor),
|
|
||||||
).paddingOnly(left: 6),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).paddingOnly(left: 12),
|
|
||||||
Card(
|
|
||||||
elevation: 1,
|
|
||||||
child: PostItem(
|
|
||||||
item: widget.item.repostTo!,
|
|
||||||
isCompact: true,
|
|
||||||
attachmentParent: widget.item.id.toString(),
|
|
||||||
).paddingSymmetric(vertical: 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
openBuilder: (_, __) => TitleShell(
|
|
||||||
title: 'postDetail'.tr,
|
|
||||||
child: PostDetailScreen(
|
|
||||||
id: widget.item.repostTo!.id.toString(),
|
|
||||||
post: widget.item.repostTo!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
closedElevation: 0,
|
|
||||||
openElevation: 0,
|
|
||||||
closedColor:
|
|
||||||
widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
|
||||||
openColor: Theme.of(context).colorScheme.surface,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildAttachments() {
|
|
||||||
final List<String> attachments = item.body['attachments'] is List
|
|
||||||
? List.from(item.body['attachments']?.whereType<String>())
|
|
||||||
: List.empty();
|
|
||||||
|
|
||||||
if (attachments.length > 3) {
|
|
||||||
return AttachmentList(
|
|
||||||
parentId: widget.item.id.toString(),
|
|
||||||
attachmentsId: attachments,
|
|
||||||
autoload: false,
|
|
||||||
isGrid: true,
|
|
||||||
).paddingOnly(left: 36, top: 4, bottom: 4);
|
|
||||||
} else if (attachments.length > 1 || AppTheme.isLargeScreen(context)) {
|
|
||||||
return AttachmentList(
|
|
||||||
parentId: widget.item.id.toString(),
|
|
||||||
attachmentsId: attachments,
|
|
||||||
autoload: false,
|
|
||||||
isColumn: true,
|
|
||||||
).paddingOnly(left: 60, right: 24, top: 4, bottom: 4);
|
|
||||||
} else {
|
|
||||||
return AttachmentList(
|
|
||||||
flatMaxHeight: MediaQuery.of(context).size.width,
|
|
||||||
parentId: widget.item.id.toString(),
|
|
||||||
attachmentsId: attachments,
|
|
||||||
autoload: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFeaturedReply() {
|
|
||||||
if ((widget.item.metric?.replyCount ?? 0) == 0) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
final List<String> attachments = item.body['attachments'] is List
|
|
||||||
? List.from(item.body['attachments']?.whereType<String>())
|
|
||||||
: List.empty();
|
|
||||||
final unFocusColor =
|
|
||||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
|
||||||
return FutureBuilder(
|
|
||||||
future: Get.find<PostProvider>().listPostFeaturedReply(
|
|
||||||
widget.item.id.toString(),
|
|
||||||
),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 480),
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: Column(
|
|
||||||
children: snapshot.data!
|
|
||||||
.map(
|
|
||||||
(x) => Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
AccountAvatar(content: x.author.avatar, radius: 10),
|
|
||||||
const Gap(6),
|
|
||||||
Text(
|
|
||||||
x.author.nick,
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const Gap(6),
|
|
||||||
Text(
|
|
||||||
format(
|
|
||||||
x.publishedAt?.toLocal() ?? DateTime.now(),
|
|
||||||
locale: 'en_short',
|
|
||||||
),
|
|
||||||
).paddingOnly(top: 0.5),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: x.body['content'],
|
|
||||||
parentId: 'p${item.id}-featured-reply${x.id}',
|
|
||||||
),
|
|
||||||
if (x.body['attachments'] is List &&
|
|
||||||
x.body['attachments'].length > 0)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.file_copy,
|
|
||||||
size: 15,
|
|
||||||
color: unFocusColor,
|
|
||||||
).paddingOnly(right: 5),
|
|
||||||
Text(
|
|
||||||
'attachmentHint'.trParams(
|
|
||||||
{
|
|
||||||
'count': x.body['attachments'].length
|
|
||||||
.toString()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
style: TextStyle(color: unFocusColor),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).paddingSymmetric(horizontal: 12, vertical: 8),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.animate()
|
|
||||||
.fadeIn(
|
|
||||||
duration: 300.ms,
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
)
|
|
||||||
.paddingOnly(
|
|
||||||
top: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
|
||||||
? 10
|
|
||||||
: 6,
|
|
||||||
left:
|
|
||||||
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
|
||||||
? 24
|
|
||||||
: 60,
|
|
||||||
right: 16,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
double _contentHeight = 0;
|
double _contentHeight = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -436,9 +83,15 @@ class _PostItemState extends State<PostItem> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildThumbnail().paddingOnly(bottom: 8),
|
_PostThumbnail(
|
||||||
_buildHeader().paddingSymmetric(horizontal: 12),
|
rid: item.body['thumbnail'],
|
||||||
_buildHeaderDivider().paddingSymmetric(horizontal: 12),
|
parentId: widget.item.id.toString(),
|
||||||
|
).paddingOnly(bottom: 8),
|
||||||
|
_PostHeaderWidget(
|
||||||
|
isCompact: widget.isCompact,
|
||||||
|
item: item,
|
||||||
|
).paddingSymmetric(horizontal: 12),
|
||||||
|
_PostHeaderDividerWidget(item: item).paddingSymmetric(horizontal: 12),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
SizedContainer(
|
SizedContainer(
|
||||||
@ -448,10 +101,14 @@ class _PostItemState extends State<PostItem> {
|
|||||||
onChange: (size) {
|
onChange: (size) {
|
||||||
setState(() => _contentHeight = size.height);
|
setState(() => _contentHeight = size.height);
|
||||||
},
|
},
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: MarkdownTextContent(
|
child: MarkdownTextContent(
|
||||||
parentId: 'p${item.id}',
|
parentId: 'p${item.id}',
|
||||||
content: item.body['content'],
|
content: item.body['content'],
|
||||||
|
isAutoWarp: item.type == 'story',
|
||||||
isSelectable: widget.isContentSelectable,
|
isSelectable: widget.isContentSelectable,
|
||||||
|
),
|
||||||
).paddingOnly(
|
).paddingOnly(
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 12,
|
right: 12,
|
||||||
@ -489,7 +146,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
right: 8,
|
right: 8,
|
||||||
top: 4,
|
top: 4,
|
||||||
),
|
),
|
||||||
_buildFooter().paddingOnly(left: 16),
|
_PostFooterWidget(item: item).paddingOnly(left: 16),
|
||||||
if (attachments.isNotEmpty)
|
if (attachments.isNotEmpty)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -515,7 +172,10 @@ class _PostItemState extends State<PostItem> {
|
|||||||
closedBuilder: (_, openContainer) => Column(
|
closedBuilder: (_, openContainer) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildThumbnail().paddingOnly(bottom: 4),
|
_PostThumbnail(
|
||||||
|
rid: item.body['thumbnail'],
|
||||||
|
parentId: widget.item.id.toString(),
|
||||||
|
).paddingOnly(bottom: 4),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -537,8 +197,11 @@ class _PostItemState extends State<PostItem> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildHeader(),
|
_PostHeaderWidget(
|
||||||
_buildHeaderDivider(),
|
isCompact: widget.isCompact,
|
||||||
|
item: item,
|
||||||
|
),
|
||||||
|
_PostHeaderDividerWidget(item: item),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
SizedContainer(
|
SizedContainer(
|
||||||
@ -549,15 +212,19 @@ class _PostItemState extends State<PostItem> {
|
|||||||
onChange: (size) {
|
onChange: (size) {
|
||||||
setState(() => _contentHeight = size.height);
|
setState(() => _contentHeight = size.height);
|
||||||
},
|
},
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: MarkdownTextContent(
|
child: MarkdownTextContent(
|
||||||
parentId: 'p${item.id}-embed',
|
parentId: 'p${item.id}-embed',
|
||||||
content: item.body['content'],
|
content: item.body['content'],
|
||||||
|
isAutoWarp: item.type == 'story',
|
||||||
isSelectable: widget.isContentSelectable,
|
isSelectable: widget.isContentSelectable,
|
||||||
isLargeText: item.type == 'article' &&
|
isLargeText: item.type == 'article' &&
|
||||||
widget.isFullContent,
|
widget.isFullContent,
|
||||||
).paddingOnly(left: 12, right: 8),
|
).paddingOnly(left: 12, right: 8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (_contentHeight >= 320 && !widget.isFullContent)
|
if (_contentHeight >= 320 && !widget.isFullContent)
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
@ -569,10 +236,14 @@ class _PostItemState extends State<PostItem> {
|
|||||||
begin: Alignment.bottomCenter,
|
begin: Alignment.bottomCenter,
|
||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Theme.of(context).colorScheme.surface,
|
(widget.backgroundColor ??
|
||||||
Theme.of(context)
|
Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.surface
|
.surface),
|
||||||
|
(widget.backgroundColor ??
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface)
|
||||||
.withOpacity(0),
|
.withOpacity(0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -586,15 +257,33 @@ class _PostItemState extends State<PostItem> {
|
|||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 480),
|
constraints: const BoxConstraints(maxWidth: 480),
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
child: _buildReply(context),
|
child: _PostEmbedWidget(
|
||||||
|
isClickable: widget.isClickable,
|
||||||
|
isOverrideEmbedClickable:
|
||||||
|
widget.isOverrideEmbedClickable,
|
||||||
|
item: widget.item.replyTo!,
|
||||||
|
username: widget.item.replyTo!.author.name,
|
||||||
|
hintText: 'postRepliedNotify',
|
||||||
|
icon: FontAwesomeIcons.reply,
|
||||||
|
id: widget.item.replyTo!.id.toString(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (widget.item.repostTo != null && widget.isShowEmbed)
|
if (widget.item.repostTo != null && widget.isShowEmbed)
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 480),
|
constraints: const BoxConstraints(maxWidth: 480),
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
child: _buildRepost(context),
|
child: _PostEmbedWidget(
|
||||||
|
isClickable: widget.isClickable,
|
||||||
|
isOverrideEmbedClickable:
|
||||||
|
widget.isOverrideEmbedClickable,
|
||||||
|
item: widget.item.repostTo!,
|
||||||
|
username: widget.item.repostTo!.author.name,
|
||||||
|
hintText: 'postRepostedNotify',
|
||||||
|
icon: FontAwesomeIcons.retweet,
|
||||||
|
id: widget.item.repostTo!.id.toString(),
|
||||||
),
|
),
|
||||||
_buildFooter().paddingOnly(left: 12),
|
),
|
||||||
|
_PostFooterWidget(item: item).paddingOnly(left: 12),
|
||||||
LinkExpansion(content: item.body['content'])
|
LinkExpansion(content: item.body['content'])
|
||||||
.paddingOnly(top: 4),
|
.paddingOnly(top: 4),
|
||||||
],
|
],
|
||||||
@ -610,8 +299,8 @@ class _PostItemState extends State<PostItem> {
|
|||||||
right: 16,
|
right: 16,
|
||||||
left: 16,
|
left: 16,
|
||||||
),
|
),
|
||||||
_buildAttachments(),
|
_PostAttachmentWidget(item: item),
|
||||||
if (widget.showFeaturedReply) _buildFeaturedReply(),
|
if (widget.showFeaturedReply) _PostFeaturedReplyWidget(item: item),
|
||||||
if (widget.isShowReply || widget.isReactable)
|
if (widget.isShowReply || widget.isReactable)
|
||||||
PostQuickAction(
|
PostQuickAction(
|
||||||
isShowReply: widget.isShowReply,
|
isShowReply: widget.isShowReply,
|
||||||
@ -654,6 +343,400 @@ class _PostItemState extends State<PostItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostFeaturedReplyWidget extends StatelessWidget {
|
||||||
|
final Post item;
|
||||||
|
|
||||||
|
const _PostFeaturedReplyWidget({required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isLargeScreen = AppTheme.isLargeScreen(context);
|
||||||
|
final unFocusColor =
|
||||||
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
|
|
||||||
|
if ((item.metric?.replyCount ?? 0) == 0) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> attachments = item.body['attachments'] is List
|
||||||
|
? List.from(item.body['attachments']?.whereType<String>())
|
||||||
|
: List.empty();
|
||||||
|
|
||||||
|
return FutureBuilder(
|
||||||
|
future:
|
||||||
|
Get.find<PostProvider>().listPostFeaturedReply(item.id.toString()),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 480),
|
||||||
|
child: Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
children: snapshot.data!
|
||||||
|
.map(
|
||||||
|
(reply) => ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: OpenContainer(
|
||||||
|
closedBuilder: (_, openContainer) => Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AccountAvatar(
|
||||||
|
content: reply.author.avatar,
|
||||||
|
radius: 10,
|
||||||
|
),
|
||||||
|
const Gap(6),
|
||||||
|
Text(
|
||||||
|
reply.author.nick,
|
||||||
|
style:
|
||||||
|
const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const Gap(6),
|
||||||
|
Text(
|
||||||
|
format(
|
||||||
|
reply.publishedAt?.toLocal() ?? DateTime.now(),
|
||||||
|
locale: 'en_short',
|
||||||
|
),
|
||||||
|
).paddingOnly(top: 0.5),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MarkdownTextContent(
|
||||||
|
isAutoWarp: reply.type == 'story',
|
||||||
|
content: reply.body['content'],
|
||||||
|
parentId:
|
||||||
|
'p${item.id}-featured-reply${reply.id}',
|
||||||
|
),
|
||||||
|
if (reply.body['attachments'] is List &&
|
||||||
|
reply.body['attachments'].isNotEmpty)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.file_copy,
|
||||||
|
size: 15,
|
||||||
|
color: unFocusColor,
|
||||||
|
).paddingOnly(right: 5),
|
||||||
|
Text(
|
||||||
|
'attachmentHint'.trParams(
|
||||||
|
{
|
||||||
|
'count': reply
|
||||||
|
.body['attachments'].length
|
||||||
|
.toString(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
style: TextStyle(color: unFocusColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingSymmetric(horizontal: 12, vertical: 8),
|
||||||
|
openBuilder: (_, __) => TitleShell(
|
||||||
|
title: 'postDetail'.tr,
|
||||||
|
child: PostDetailScreen(
|
||||||
|
id: reply.id.toString(),
|
||||||
|
post: reply,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
closedElevation: 0,
|
||||||
|
openElevation: 0,
|
||||||
|
closedColor:
|
||||||
|
Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
openColor: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.animate()
|
||||||
|
.fadeIn(
|
||||||
|
duration: 300.ms,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
)
|
||||||
|
.paddingOnly(
|
||||||
|
top: (attachments.length == 1 && !isLargeScreen) ? 10 : 6,
|
||||||
|
left: (attachments.length == 1 && !isLargeScreen) ? 24 : 60,
|
||||||
|
right: 16,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostAttachmentWidget extends StatelessWidget {
|
||||||
|
final Post item;
|
||||||
|
|
||||||
|
const _PostAttachmentWidget({required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isLargeScreen = AppTheme.isLargeScreen(context);
|
||||||
|
|
||||||
|
final List<String> attachments = item.body['attachments'] is List
|
||||||
|
? List.from(item.body['attachments']?.whereType<String>())
|
||||||
|
: List.empty();
|
||||||
|
|
||||||
|
if (attachments.length > 3) {
|
||||||
|
return AttachmentList(
|
||||||
|
parentId: item.id.toString(),
|
||||||
|
attachmentsId: attachments,
|
||||||
|
autoload: false,
|
||||||
|
isGrid: true,
|
||||||
|
).paddingOnly(left: 36, top: 4, bottom: 4);
|
||||||
|
} else if (attachments.length > 1 || isLargeScreen) {
|
||||||
|
return AttachmentList(
|
||||||
|
parentId: item.id.toString(),
|
||||||
|
attachmentsId: attachments,
|
||||||
|
autoload: false,
|
||||||
|
isColumn: true,
|
||||||
|
).paddingOnly(left: 60, right: 24, top: 4, bottom: 4);
|
||||||
|
} else {
|
||||||
|
return AttachmentList(
|
||||||
|
flatMaxHeight: MediaQuery.of(context).size.width,
|
||||||
|
parentId: item.id.toString(),
|
||||||
|
attachmentsId: attachments,
|
||||||
|
autoload: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostEmbedWidget extends StatelessWidget {
|
||||||
|
final bool isClickable;
|
||||||
|
final bool isOverrideEmbedClickable;
|
||||||
|
final Post item;
|
||||||
|
final String username;
|
||||||
|
final String hintText;
|
||||||
|
final IconData icon;
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
const _PostEmbedWidget({
|
||||||
|
required this.isClickable,
|
||||||
|
required this.isOverrideEmbedClickable,
|
||||||
|
required this.item,
|
||||||
|
required this.username,
|
||||||
|
required this.hintText,
|
||||||
|
required this.icon,
|
||||||
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final unFocusColor =
|
||||||
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
|
|
||||||
|
return OpenContainer(
|
||||||
|
tappable: isClickable || isOverrideEmbedClickable,
|
||||||
|
closedBuilder: (_, openContainer) => Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
FaIcon(
|
||||||
|
icon,
|
||||||
|
size: 16,
|
||||||
|
color: unFocusColor,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
hintText.trParams(
|
||||||
|
{'username': '@$username'},
|
||||||
|
),
|
||||||
|
style: TextStyle(color: unFocusColor),
|
||||||
|
).paddingOnly(left: 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingOnly(left: 12),
|
||||||
|
Card(
|
||||||
|
elevation: 1,
|
||||||
|
child: PostItem(
|
||||||
|
item: item,
|
||||||
|
isCompact: true,
|
||||||
|
attachmentParent: id,
|
||||||
|
).paddingSymmetric(vertical: 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
openBuilder: (_, __) => TitleShell(
|
||||||
|
title: 'postDetail'.tr,
|
||||||
|
child: PostDetailScreen(
|
||||||
|
id: id,
|
||||||
|
post: item,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
closedElevation: 0,
|
||||||
|
openElevation: 0,
|
||||||
|
closedColor: Theme.of(context).colorScheme.surface,
|
||||||
|
openColor: Theme.of(context).colorScheme.surface,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostHeaderDividerWidget extends StatelessWidget {
|
||||||
|
final Post item;
|
||||||
|
|
||||||
|
const _PostHeaderDividerWidget({
|
||||||
|
required this.item,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (item.body['description'] != null || item.body['title'] != null) {
|
||||||
|
return const Divider(thickness: 0.3, height: 1).paddingSymmetric(
|
||||||
|
vertical: 8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostFooterWidget extends StatelessWidget {
|
||||||
|
final Post item;
|
||||||
|
|
||||||
|
const _PostFooterWidget({required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final unFocusColor =
|
||||||
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
|
|
||||||
|
List<String> labels = List.empty(growable: true);
|
||||||
|
if (item.editedAt != null) {
|
||||||
|
labels.add('postEdited'.trParams({
|
||||||
|
'date': DateFormat('yy/M/d HH:mm').format(item.editedAt!.toLocal()),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (item.realm != null) {
|
||||||
|
labels.add('postInRealm'.trParams({
|
||||||
|
'realm': item.realm!.alias,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> widgets = List.empty(growable: true);
|
||||||
|
|
||||||
|
if (item.tags?.isNotEmpty ?? false) {
|
||||||
|
widgets.add(PostTagsList(tags: item.tags!));
|
||||||
|
}
|
||||||
|
if (labels.isNotEmpty) {
|
||||||
|
widgets.add(Text(
|
||||||
|
labels.join(' · '),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: unFocusColor,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (item.pinnedAt != null) {
|
||||||
|
widgets.add(Text(
|
||||||
|
'postPinned'.tr,
|
||||||
|
style: TextStyle(fontSize: 12, color: unFocusColor),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widgets.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
} else {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: widgets,
|
||||||
|
).paddingOnly(top: 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostHeaderWidget extends StatelessWidget {
|
||||||
|
final bool isCompact;
|
||||||
|
final Post item;
|
||||||
|
|
||||||
|
const _PostHeaderWidget({
|
||||||
|
required this.isCompact,
|
||||||
|
required this.item,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (isCompact)
|
||||||
|
AccountAvatar(
|
||||||
|
content: item.author.avatar,
|
||||||
|
radius: 10,
|
||||||
|
).paddingOnly(left: 2, top: 1),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.author.nick,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
RelativeDate(item.publishedAt?.toLocal() ?? DateTime.now())
|
||||||
|
.paddingOnly(left: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (item.body['title'] != null)
|
||||||
|
Text(
|
||||||
|
item.body['title'],
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium!
|
||||||
|
.copyWith(fontSize: 15),
|
||||||
|
),
|
||||||
|
if (item.body['description'] != null)
|
||||||
|
Text(
|
||||||
|
item.body['description'],
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingOnly(left: isCompact ? 6 : 12),
|
||||||
|
),
|
||||||
|
if (item.type == 'article')
|
||||||
|
Badge(
|
||||||
|
label: Text('article'.tr),
|
||||||
|
).paddingOnly(top: 3),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostThumbnail extends StatelessWidget {
|
||||||
|
final String parentId;
|
||||||
|
final String? rid;
|
||||||
|
|
||||||
|
const _PostThumbnail({required this.parentId, required this.rid});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (rid?.isEmpty ?? true) return const SizedBox.shrink();
|
||||||
|
final border = BorderSide(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 0.3,
|
||||||
|
);
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(border: Border(top: border, bottom: border)),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: AttachmentSelfContainedEntry(
|
||||||
|
rid: rid!,
|
||||||
|
parentId: 'p$parentId-thumbnail',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef _OnWidgetSizeChange = void Function(Size size);
|
typedef _OnWidgetSizeChange = void Function(Size size);
|
||||||
|
|
||||||
class _MeasureSizeRenderObject extends RenderProxyBox {
|
class _MeasureSizeRenderObject extends RenderProxyBox {
|
||||||
|
@ -27,7 +27,7 @@ class PostTagsList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
AppRouter.instance.pushNamed('feedSearch', queryParameters: {
|
AppRouter.instance.pushNamed('postSearch', queryParameters: {
|
||||||
'tag': x.alias,
|
'tag': x.alias,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
23
lib/widgets/relative_date.dart
Normal file
23
lib/widgets/relative_date.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:timeago/timeago.dart';
|
||||||
|
|
||||||
|
class RelativeDate extends StatelessWidget {
|
||||||
|
final DateTime date;
|
||||||
|
final bool isFull;
|
||||||
|
|
||||||
|
const RelativeDate(this.date, {super.key, this.isFull = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isFull) {
|
||||||
|
return Text(DateFormat('y/M/d HH:mm').format(date));
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
format(
|
||||||
|
date,
|
||||||
|
locale: 'en_short',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ import flutter_local_notifications
|
|||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import gal
|
import gal
|
||||||
|
import in_app_review
|
||||||
import livekit_client
|
import livekit_client
|
||||||
import macos_window_utils
|
import macos_window_utils
|
||||||
import media_kit_libs_macos_video
|
import media_kit_libs_macos_video
|
||||||
@ -46,6 +47,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||||
|
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||||
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
||||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||||
|
@ -158,6 +158,8 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (8.0.2):
|
- GoogleUtilities/UserDefaults (8.0.2):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
|
- in_app_review (0.2.0):
|
||||||
|
- FlutterMacOS
|
||||||
- livekit_client (2.2.6):
|
- livekit_client (2.2.6):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 125.6422.04)
|
- WebRTC-SDK (= 125.6422.04)
|
||||||
@ -234,6 +236,7 @@ DEPENDENCIES:
|
|||||||
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
||||||
|
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
|
||||||
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
||||||
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
|
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
|
||||||
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
||||||
@ -299,6 +302,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
gal:
|
gal:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
||||||
|
in_app_review:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos
|
||||||
livekit_client:
|
livekit_client:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
|
||||||
macos_window_utils:
|
macos_window_utils:
|
||||||
@ -336,7 +341,7 @@ SPEC CHECKSUMS:
|
|||||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||||
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
||||||
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
|
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
|
||||||
file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2
|
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||||
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9
|
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9
|
||||||
firebase_analytics: a2d0d907566e4a48e27745317f05b4b7db85edd9
|
firebase_analytics: a2d0d907566e4a48e27745317f05b4b7db85edd9
|
||||||
firebase_core: c55630cdb8a01cf49eae741dd4bc8c93bdd546b8
|
firebase_core: c55630cdb8a01cf49eae741dd4bc8c93bdd546b8
|
||||||
@ -359,6 +364,7 @@ SPEC CHECKSUMS:
|
|||||||
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de
|
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||||
|
in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0
|
||||||
livekit_client: 98d09566e3a936b3402be8091ec3845556d36800
|
livekit_client: 98d09566e3a936b3402be8091ec3845556d36800
|
||||||
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
|
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
|
||||||
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||||
@ -377,7 +383,7 @@ SPEC CHECKSUMS:
|
|||||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
|
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
|
||||||
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
|
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
|
||||||
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
|
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||||
WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3
|
WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3
|
||||||
|
|
||||||
|
@ -47,6 +47,11 @@
|
|||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
|
<key>CFBundleLocalizations</key>
|
||||||
|
<array>
|
||||||
|
<string>zh_CN</string>
|
||||||
|
<string>en</string>
|
||||||
|
</array>
|
||||||
<key>NSUserActivityTypes</key>
|
<key>NSUserActivityTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>INStartCallIntent</string>
|
<string>INStartCallIntent</string>
|
||||||
|
28
pubspec.lock
28
pubspec.lock
@ -474,10 +474,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_macos
|
name: file_selector_macos
|
||||||
sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385
|
sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.4"
|
version: "0.9.4+1"
|
||||||
file_selector_platform_interface:
|
file_selector_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -751,10 +751,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_launcher_icons
|
name: flutter_launcher_icons
|
||||||
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
|
sha256: a38f2f1b3c373d42bf08bd17d60e20d3c73abce7727607b4d085ec7d5acaa294
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.1"
|
version: "0.14.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -1125,6 +1125,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.1+1"
|
||||||
|
in_app_review:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: in_app_review
|
||||||
|
sha256: "99869244d09adc76af16bf8fd731dd13cef58ecafd5917847589c49f378cbb30"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.9"
|
||||||
|
in_app_review_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: in_app_review_platform_interface
|
||||||
|
sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
infinite_scroll_pagination:
|
infinite_scroll_pagination:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -2078,10 +2094,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.1"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -2,7 +2,7 @@ name: solian
|
|||||||
description: "The Solar Network App"
|
description: "The Solar Network App"
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 1.2.2+3
|
version: 1.2.4+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.4 <4.0.0"
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
@ -83,13 +83,14 @@ dependencies:
|
|||||||
flutter_app_update: ^3.1.0
|
flutter_app_update: ^3.1.0
|
||||||
version: ^3.0.2
|
version: ^3.0.2
|
||||||
action_slider: ^0.7.0
|
action_slider: ^0.7.0
|
||||||
|
in_app_review: ^2.0.9
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
flutter_lints: ^4.0.0
|
flutter_lints: ^4.0.0
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.14.0
|
||||||
|
|
||||||
build_runner: ^2.4.12
|
build_runner: ^2.4.12
|
||||||
flutter_native_splash: ^2.4.1
|
flutter_native_splash: ^2.4.1
|
||||||
|
Reference in New Issue
Block a user