Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
e4b741ff0c | |||
e69abb7f9d | |||
565a8e41cc | |||
c9fbe47337 | |||
01db63e297 | |||
d87e67bd17 | |||
06aa1fb359 | |||
62733bf29f | |||
ce16de9c71 | |||
47eb6cbc66 | |||
029e72fb0b | |||
152efd97a0 | |||
ad1dc064e6 | |||
675b5dea5d | |||
5941cb9fd5 | |||
e11bf204af |
@ -157,6 +157,9 @@
|
|||||||
"postListNews": "News",
|
"postListNews": "News",
|
||||||
"postListFriends": "Friends",
|
"postListFriends": "Friends",
|
||||||
"postListShuffle": "Random",
|
"postListShuffle": "Random",
|
||||||
|
"attachmentThumbnail": "Thumbnail",
|
||||||
|
"attachmentThumbnailAttachmentNew": "Upload thumbnail",
|
||||||
|
"attachmentThumbnailAttachment": "Attachment serial number",
|
||||||
"postEditorModeStory": "Post a post",
|
"postEditorModeStory": "Post a post",
|
||||||
"postEditorModeArticle": "Post an article",
|
"postEditorModeArticle": "Post an article",
|
||||||
"postEditor": "Post editor",
|
"postEditor": "Post editor",
|
||||||
@ -225,6 +228,8 @@
|
|||||||
"realmDescription": "Description",
|
"realmDescription": "Description",
|
||||||
"realmPublic": "Public Realm",
|
"realmPublic": "Public Realm",
|
||||||
"realmCommunity": "Community Realm",
|
"realmCommunity": "Community Realm",
|
||||||
|
"realmAvatar": "Realm avatar",
|
||||||
|
"realmBanner": "Realm banner",
|
||||||
"realmDetail": "Realm detail",
|
"realmDetail": "Realm detail",
|
||||||
"realmMember": "Realm member",
|
"realmMember": "Realm member",
|
||||||
"realmMembers": "Realm members",
|
"realmMembers": "Realm members",
|
||||||
@ -250,7 +255,8 @@
|
|||||||
"channelName": "Name",
|
"channelName": "Name",
|
||||||
"channelDescription": "Description",
|
"channelDescription": "Description",
|
||||||
"channelDirectDescription": "Direct message with @username",
|
"channelDirectDescription": "Direct message with @username",
|
||||||
"channelEncrypted": "Encrypted Channel",
|
"channelPublic": "Public channel",
|
||||||
|
"channelCommunity": "Community channel",
|
||||||
"channelMember": "Channel member",
|
"channelMember": "Channel member",
|
||||||
"channelMembers": "Channel members",
|
"channelMembers": "Channel members",
|
||||||
"channelMembersAdd": "Add channel members",
|
"channelMembersAdd": "Add channel members",
|
||||||
@ -407,5 +413,14 @@
|
|||||||
"userLevel13": "Immortal",
|
"userLevel13": "Immortal",
|
||||||
"postBrowsingIn": "Browsing in @region",
|
"postBrowsingIn": "Browsing in @region",
|
||||||
"needRestartToApply": "Restart the application to take effect",
|
"needRestartToApply": "Restart the application to take effect",
|
||||||
"holdToSeeDetail": "Long press / Mouse hover to see detail"
|
"holdToSeeDetail": "Long press / Mouse hover to see detail",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"subscribed": "Subscribed",
|
||||||
|
"unsubscribe": "Unsubscribe",
|
||||||
|
"preferences": "Preferences",
|
||||||
|
"notificationPreferences": "Notification preferences",
|
||||||
|
"notificationTopicPostFeedback": "Post feedbacks",
|
||||||
|
"notificationTopicPostSubscription": "Post subscriptions",
|
||||||
|
"preferencesApplied": "Preferences has been applied.",
|
||||||
|
"save": "Save"
|
||||||
}
|
}
|
||||||
|
@ -168,6 +168,9 @@
|
|||||||
"postListNews": "新鲜事",
|
"postListNews": "新鲜事",
|
||||||
"postListFriends": "好友圈",
|
"postListFriends": "好友圈",
|
||||||
"postListShuffle": "打乱看",
|
"postListShuffle": "打乱看",
|
||||||
|
"attachmentThumbnail": "附件缩略图",
|
||||||
|
"attachmentThumbnailAttachmentNew": "上传附件作为缩略图",
|
||||||
|
"attachmentThumbnailAttachment": "附件序列号",
|
||||||
"postNew": "创建新帖子",
|
"postNew": "创建新帖子",
|
||||||
"postNewInRealmHint": "在领域 @realm 里发表新帖子",
|
"postNewInRealmHint": "在领域 @realm 里发表新帖子",
|
||||||
"postAction": "发表",
|
"postAction": "发表",
|
||||||
@ -226,6 +229,8 @@
|
|||||||
"realmDescription": "领域简介",
|
"realmDescription": "领域简介",
|
||||||
"realmPublic": "公开领域",
|
"realmPublic": "公开领域",
|
||||||
"realmCommunity": "社区领域",
|
"realmCommunity": "社区领域",
|
||||||
|
"realmAvatar": "领域头像",
|
||||||
|
"realmBanner": "领域横幅",
|
||||||
"realmDetail": "领域详情",
|
"realmDetail": "领域详情",
|
||||||
"realmMember": "领域成员",
|
"realmMember": "领域成员",
|
||||||
"realmMembers": "领域成员",
|
"realmMembers": "领域成员",
|
||||||
@ -251,7 +256,8 @@
|
|||||||
"channelName": "显示名称",
|
"channelName": "显示名称",
|
||||||
"channelDescription": "频道简介",
|
"channelDescription": "频道简介",
|
||||||
"channelDirectDescription": "与 @username 的私聊",
|
"channelDirectDescription": "与 @username 的私聊",
|
||||||
"channelEncrypted": "加密频道",
|
"channelPublic": "公开频道",
|
||||||
|
"channelCommunity": "社区频道",
|
||||||
"channelMember": "频道成员",
|
"channelMember": "频道成员",
|
||||||
"channelMembers": "频道成员",
|
"channelMembers": "频道成员",
|
||||||
"channelMembersAdd": "添加频道成员",
|
"channelMembersAdd": "添加频道成员",
|
||||||
@ -408,5 +414,14 @@
|
|||||||
"userLevel13": "万古流芳",
|
"userLevel13": "万古流芳",
|
||||||
"postBrowsingIn": "浏览 @region 内的帖子中",
|
"postBrowsingIn": "浏览 @region 内的帖子中",
|
||||||
"needRestartToApply": "需要重启应用来生效",
|
"needRestartToApply": "需要重启应用来生效",
|
||||||
"holdToSeeDetail": "长按 / 鼠标悬浮来查看详情"
|
"holdToSeeDetail": "长按 / 鼠标悬浮来查看详情",
|
||||||
|
"subscribe": "订阅",
|
||||||
|
"subscribed": "已订阅",
|
||||||
|
"unsubscribe": "取消订阅",
|
||||||
|
"preferences": "偏好设置",
|
||||||
|
"notificationPreferences": "通知偏好设置",
|
||||||
|
"notificationTopicPostFeedback": "帖子反馈",
|
||||||
|
"notificationTopicPostSubscription": "订阅源",
|
||||||
|
"preferencesApplied": "偏好设置已应用",
|
||||||
|
"save": "保存"
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ PODS:
|
|||||||
- Firebase/Performance (= 11.0.0)
|
- Firebase/Performance (= 11.0.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseABTesting (11.1.0):
|
- FirebaseABTesting (11.2.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- FirebaseAnalytics (11.0.0):
|
- FirebaseAnalytics (11.0.0):
|
||||||
- FirebaseAnalytics/AdIdSupport (= 11.0.0)
|
- FirebaseAnalytics/AdIdSupport (= 11.0.0)
|
||||||
@ -97,9 +97,9 @@ PODS:
|
|||||||
- FirebaseCoreInternal (~> 11.0)
|
- FirebaseCoreInternal (~> 11.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/Logger (~> 8.0)
|
- GoogleUtilities/Logger (~> 8.0)
|
||||||
- FirebaseCoreExtension (11.1.0):
|
- FirebaseCoreExtension (11.2.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- FirebaseCoreInternal (11.1.0):
|
- FirebaseCoreInternal (11.2.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- FirebaseCrashlytics (11.0.0):
|
- FirebaseCrashlytics (11.0.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
@ -110,7 +110,7 @@ PODS:
|
|||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseInstallations (11.1.0):
|
- FirebaseInstallations (11.2.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
@ -134,7 +134,7 @@ PODS:
|
|||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseRemoteConfig (11.1.0):
|
- FirebaseRemoteConfig (11.2.0):
|
||||||
- FirebaseABTesting (~> 11.0)
|
- FirebaseABTesting (~> 11.0)
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
@ -142,8 +142,8 @@ PODS:
|
|||||||
- FirebaseSharedSwift (~> 11.0)
|
- FirebaseSharedSwift (~> 11.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- FirebaseRemoteConfigInterop (11.1.0)
|
- FirebaseRemoteConfigInterop (11.2.0)
|
||||||
- FirebaseSessions (11.1.0):
|
- FirebaseSessions (11.2.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- FirebaseCoreExtension (~> 11.0)
|
- FirebaseCoreExtension (~> 11.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
@ -152,7 +152,7 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesSwift (~> 2.1)
|
- PromisesSwift (~> 2.1)
|
||||||
- FirebaseSharedSwift (11.1.0)
|
- FirebaseSharedSwift (11.2.0)
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_background_service_ios (0.0.3):
|
- flutter_background_service_ios (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -450,19 +450,19 @@ SPEC CHECKSUMS:
|
|||||||
firebase_crashlytics: 4111f8198b78c99471c955af488cecd8224967e6
|
firebase_crashlytics: 4111f8198b78c99471c955af488cecd8224967e6
|
||||||
firebase_messaging: c40f84e7a98da956d5262fada373b5c458edcf13
|
firebase_messaging: c40f84e7a98da956d5262fada373b5c458edcf13
|
||||||
firebase_performance: 8b7b9ca5adf3a9b3afa12b4eb96b9cabefc2c248
|
firebase_performance: 8b7b9ca5adf3a9b3afa12b4eb96b9cabefc2c248
|
||||||
FirebaseABTesting: c2e22c3aab99afa81d0561708b2c1c356c556976
|
FirebaseABTesting: 2104d957ce33888a3d6f3bde298cdee376dde8f1
|
||||||
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
|
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
|
||||||
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
|
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
|
||||||
FirebaseCoreExtension: aa5c9779c2d0d39d83f1ceb3fdbafe80c4feecfa
|
FirebaseCoreExtension: cda74ddfb001224bd8fd1d6e74698b4ed07803de
|
||||||
FirebaseCoreInternal: adefedc9a88dbe393c4884640a73ec9e8e790f8c
|
FirebaseCoreInternal: 0c569513412da9f3b31bd0b340013bbee8f295c5
|
||||||
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b
|
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b
|
||||||
FirebaseInstallations: d0a8fea5a6fa91abc661591cf57c0f0d70863e57
|
FirebaseInstallations: 771177d89d6c451dc6e50085ec82e2fc77ed0a4a
|
||||||
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742
|
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742
|
||||||
FirebasePerformance: efdc02bacb1b4710588c9f867011605c081cdf79
|
FirebasePerformance: efdc02bacb1b4710588c9f867011605c081cdf79
|
||||||
FirebaseRemoteConfig: 05521e937b72e01847a7128da5a492327364c705
|
FirebaseRemoteConfig: fca0b2d017fc1de52b28a4e5bcf2007c1a840457
|
||||||
FirebaseRemoteConfigInterop: abf8b1bbc0bf1b84abd22b66746926410bf91a87
|
FirebaseRemoteConfigInterop: 477b26fdeb8fb5fbaf22fa9db5343b42289dc7db
|
||||||
FirebaseSessions: 78f137e68dc01ca71606169ba4ac73b98c13752a
|
FirebaseSessions: adcec8b72d0066a385e3affcd1bcb1ebb3908ce6
|
||||||
FirebaseSharedSwift: 260a35e08943ec810d820a70bc0359136351d0c5
|
FirebaseSharedSwift: 7a0d78d155ede78407f0fdc89fbc914014c7c540
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
|
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
|
||||||
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||||
|
@ -616,6 +616,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
@ -920,6 +921,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
@ -947,6 +949,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
@ -9,7 +9,6 @@ import 'package:solian/providers/auth.dart';
|
|||||||
import 'package:solian/providers/content/channel.dart';
|
import 'package:solian/providers/content/channel.dart';
|
||||||
import 'package:solian/providers/content/realm.dart';
|
import 'package:solian/providers/content/realm.dart';
|
||||||
import 'package:solian/providers/relation.dart';
|
import 'package:solian/providers/relation.dart';
|
||||||
import 'package:solian/providers/stickers.dart';
|
|
||||||
import 'package:solian/providers/theme_switcher.dart';
|
import 'package:solian/providers/theme_switcher.dart';
|
||||||
import 'package:solian/providers/websocket.dart';
|
import 'package:solian/providers/websocket.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
@ -115,7 +114,6 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
try {
|
try {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
Get.find<StickerProvider>().refreshAvailableStickers(),
|
|
||||||
if (auth.isAuthorized.isTrue)
|
if (auth.isAuthorized.isTrue)
|
||||||
Get.find<ChannelProvider>().refreshAvailableChannel(),
|
Get.find<ChannelProvider>().refreshAvailableChannel(),
|
||||||
if (auth.isAuthorized.isTrue)
|
if (auth.isAuthorized.isTrue)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/models/event.dart';
|
import 'package:solian/models/event.dart';
|
||||||
@ -30,14 +32,31 @@ class ChatEventController {
|
|||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
|
|
||||||
isLoading.value = true;
|
const firstTake = 20;
|
||||||
await syncLocal(channel, take: 10);
|
const furtherTake = 100;
|
||||||
|
|
||||||
src.pullRemoteEvents(channel, scope: scope, take: 10).then((result) {
|
isLoading.value = true;
|
||||||
totalEvents.value = result?.$2 ?? 0;
|
await syncLocal(channel, take: firstTake);
|
||||||
syncLocal(channel, take: 10);
|
|
||||||
});
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|
||||||
|
// Take a small range of messages to check is local database up to date
|
||||||
|
var isUpToDate = true;
|
||||||
|
final result =
|
||||||
|
await src.pullRemoteEvents(channel, scope: scope, take: firstTake);
|
||||||
|
totalEvents.value = result?.$2 ?? 0;
|
||||||
|
if ((result?.$1.length ?? 0) > 0) {
|
||||||
|
final minId = result!.$1.map((x) => x.id).reduce(math.min);
|
||||||
|
isUpToDate = await src.getEventFromLocal(minId) != null;
|
||||||
|
}
|
||||||
|
syncLocal(channel, take: firstTake);
|
||||||
|
|
||||||
|
if (!isUpToDate) {
|
||||||
|
// Loading more content due to isn't up to date
|
||||||
|
final result =
|
||||||
|
await src.pullRemoteEvents(channel, scope: scope, take: furtherTake);
|
||||||
|
totalEvents.value = result?.$2 ?? 0;
|
||||||
|
syncLocal(channel, take: furtherTake);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadEvents(Channel channel, String scope) async {
|
Future<void> loadEvents(Channel channel, String scope) async {
|
||||||
@ -46,7 +65,9 @@ class ChatEventController {
|
|||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
await syncLocal(channel, take: take, offset: offset);
|
await syncLocal(channel, take: take, offset: offset);
|
||||||
src.pullRemoteEvents(channel, scope: scope, offset: offset).then((result) {
|
src
|
||||||
|
.pullRemoteEvents(channel, scope: scope, take: take, offset: offset)
|
||||||
|
.then((result) {
|
||||||
totalEvents.value = result?.$2 ?? 0;
|
totalEvents.value = result?.$2 ?? 0;
|
||||||
syncLocal(channel, take: take, offset: offset);
|
syncLocal(channel, take: take, offset: offset);
|
||||||
});
|
});
|
||||||
@ -56,7 +77,11 @@ class ChatEventController {
|
|||||||
Future<bool> syncLocal(Channel channel,
|
Future<bool> syncLocal(Channel channel,
|
||||||
{required int take, int offset = 0}) async {
|
{required int take, int offset = 0}) async {
|
||||||
final data = await src.listEvents(channel, take: take, offset: offset);
|
final data = await src.listEvents(channel, take: take, offset: offset);
|
||||||
currentEvents.replaceRange(0, currentEvents.length, data);
|
if (currentEvents.length >= offset + take) {
|
||||||
|
currentEvents.replaceRange(offset, offset + take, data);
|
||||||
|
} else {
|
||||||
|
currentEvents.insertAll(currentEvents.length, data);
|
||||||
|
}
|
||||||
for (final x in data.reversed) {
|
for (final x in data.reversed) {
|
||||||
applyEvent(x);
|
applyEvent(x);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import 'package:solian/providers/last_read.dart';
|
|||||||
import 'package:solian/providers/link_expander.dart';
|
import 'package:solian/providers/link_expander.dart';
|
||||||
import 'package:solian/providers/navigation.dart';
|
import 'package:solian/providers/navigation.dart';
|
||||||
import 'package:solian/providers/stickers.dart';
|
import 'package:solian/providers/stickers.dart';
|
||||||
|
import 'package:solian/providers/subscription.dart';
|
||||||
import 'package:solian/providers/theme_switcher.dart';
|
import 'package:solian/providers/theme_switcher.dart';
|
||||||
import 'package:solian/providers/websocket.dart';
|
import 'package:solian/providers/websocket.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
@ -151,6 +152,7 @@ class SolianApp extends StatelessWidget {
|
|||||||
Get.lazyPut(() => LinkExpandProvider());
|
Get.lazyPut(() => LinkExpandProvider());
|
||||||
Get.lazyPut(() => DailySignProvider());
|
Get.lazyPut(() => DailySignProvider());
|
||||||
Get.lazyPut(() => LastReadProvider());
|
Get.lazyPut(() => LastReadProvider());
|
||||||
|
Get.lazyPut(() => SubscriptionProvider());
|
||||||
|
|
||||||
Get.find<WebSocketProvider>().requestPermissions();
|
Get.find<WebSocketProvider>().requestPermissions();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'account.g.dart';
|
part 'account.g.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'account_status.g.dart';
|
part 'account_status.g.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
|
|
||||||
part 'attachment.g.dart';
|
part 'attachment.g.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
|
|
||||||
part 'auth.g.dart';
|
part 'auth.g.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
import 'package:solian/models/realm.dart';
|
import 'package:solian/models/realm.dart';
|
||||||
|
|
||||||
@ -19,7 +19,8 @@ class Channel {
|
|||||||
int accountId;
|
int accountId;
|
||||||
Realm? realm;
|
Realm? realm;
|
||||||
int? realmId;
|
int? realmId;
|
||||||
bool isEncrypted;
|
bool isPublic;
|
||||||
|
bool isCommunity;
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: true)
|
@JsonKey(includeFromJson: false, includeToJson: true)
|
||||||
bool isAvailable = false;
|
bool isAvailable = false;
|
||||||
@ -36,7 +37,8 @@ class Channel {
|
|||||||
required this.members,
|
required this.members,
|
||||||
required this.account,
|
required this.account,
|
||||||
required this.accountId,
|
required this.accountId,
|
||||||
required this.isEncrypted,
|
required this.isPublic,
|
||||||
|
required this.isCommunity,
|
||||||
required this.realm,
|
required this.realm,
|
||||||
required this.realmId,
|
required this.realmId,
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,8 @@ Channel _$ChannelFromJson(Map<String, dynamic> json) => Channel(
|
|||||||
.toList(),
|
.toList(),
|
||||||
account: Account.fromJson(json['account'] as Map<String, dynamic>),
|
account: Account.fromJson(json['account'] as Map<String, dynamic>),
|
||||||
accountId: (json['account_id'] as num).toInt(),
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
isEncrypted: json['is_encrypted'] as bool,
|
isPublic: json['is_public'] as bool,
|
||||||
|
isCommunity: json['is_community'] as bool,
|
||||||
realm: json['realm'] == null
|
realm: json['realm'] == null
|
||||||
? null
|
? null
|
||||||
: Realm.fromJson(json['realm'] as Map<String, dynamic>),
|
: Realm.fromJson(json['realm'] as Map<String, dynamic>),
|
||||||
@ -43,7 +44,8 @@ Map<String, dynamic> _$ChannelToJson(Channel instance) => <String, dynamic>{
|
|||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
'realm': instance.realm?.toJson(),
|
'realm': instance.realm?.toJson(),
|
||||||
'realm_id': instance.realmId,
|
'realm_id': instance.realmId,
|
||||||
'is_encrypted': instance.isEncrypted,
|
'is_public': instance.isPublic,
|
||||||
|
'is_community': instance.isCommunity,
|
||||||
'is_available': instance.isAvailable,
|
'is_available': instance.isAvailable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
|
|
||||||
part 'event.g.dart';
|
part 'event.g.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'link.g.dart';
|
part 'link.g.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'notification.g.dart';
|
part 'notification.g.dart';
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'packet.g.dart';
|
part 'packet.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class NetworkPackage {
|
class NetworkPackage {
|
||||||
@JsonKey(name: 'w')
|
@JsonKey(name: 'w', defaultValue: 'unknown')
|
||||||
String method;
|
String method;
|
||||||
@JsonKey(name: 'e')
|
@JsonKey(name: 'e')
|
||||||
String? endpoint;
|
String? endpoint;
|
||||||
|
@ -8,7 +8,7 @@ part of 'packet.dart';
|
|||||||
|
|
||||||
NetworkPackage _$NetworkPackageFromJson(Map<String, dynamic> json) =>
|
NetworkPackage _$NetworkPackageFromJson(Map<String, dynamic> json) =>
|
||||||
NetworkPackage(
|
NetworkPackage(
|
||||||
method: json['w'] as String,
|
method: json['w'] as String? ?? 'unknown',
|
||||||
endpoint: json['e'] as String?,
|
endpoint: json['e'] as String?,
|
||||||
message: json['m'] as String?,
|
message: json['m'] as String?,
|
||||||
payload: json['p'] as Map<String, dynamic>?,
|
payload: json['p'] as Map<String, dynamic>?,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'pagination.g.dart';
|
part 'pagination.g.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
import 'package:solian/models/post_categories.dart';
|
import 'package:solian/models/post_categories.dart';
|
||||||
import 'package:solian/models/realm.dart';
|
import 'package:solian/models/realm.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'post_categories.g.dart';
|
part 'post_categories.g.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
|
|
||||||
part 'realm.g.dart';
|
part 'realm.g.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
|
|
||||||
part 'relations.g.dart';
|
part 'relations.g.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
41
lib/models/subscription.dart
Normal file
41
lib/models/subscription.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:solian/models/account.dart';
|
||||||
|
import 'package:solian/models/post_categories.dart';
|
||||||
|
|
||||||
|
part 'subscription.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Subscription {
|
||||||
|
int id;
|
||||||
|
DateTime createdAt;
|
||||||
|
DateTime updatedAt;
|
||||||
|
DateTime? deletedAt;
|
||||||
|
int followerId;
|
||||||
|
Account follower;
|
||||||
|
int? accountId;
|
||||||
|
Account? account;
|
||||||
|
int? tagId;
|
||||||
|
Tag? tag;
|
||||||
|
int? categoryId;
|
||||||
|
Category? category;
|
||||||
|
|
||||||
|
Subscription({
|
||||||
|
required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.followerId,
|
||||||
|
required this.follower,
|
||||||
|
required this.accountId,
|
||||||
|
required this.account,
|
||||||
|
required this.tagId,
|
||||||
|
required this.tag,
|
||||||
|
required this.categoryId,
|
||||||
|
required this.category,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Subscription.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SubscriptionFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$SubscriptionToJson(this);
|
||||||
|
}
|
46
lib/models/subscription.g.dart
Normal file
46
lib/models/subscription.g.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'subscription.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Subscription _$SubscriptionFromJson(Map<String, dynamic> json) => Subscription(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
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),
|
||||||
|
followerId: (json['follower_id'] as num).toInt(),
|
||||||
|
follower: Account.fromJson(json['follower'] as Map<String, dynamic>),
|
||||||
|
accountId: (json['account_id'] as num?)?.toInt(),
|
||||||
|
account: json['account'] == null
|
||||||
|
? null
|
||||||
|
: Account.fromJson(json['account'] as Map<String, dynamic>),
|
||||||
|
tagId: (json['tag_id'] as num?)?.toInt(),
|
||||||
|
tag: json['tag'] == null
|
||||||
|
? null
|
||||||
|
: Tag.fromJson(json['tag'] as Map<String, dynamic>),
|
||||||
|
categoryId: (json['category_id'] as num?)?.toInt(),
|
||||||
|
category: json['category'] == null
|
||||||
|
? null
|
||||||
|
: Category.fromJson(json['category'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SubscriptionToJson(Subscription instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'follower_id': instance.followerId,
|
||||||
|
'follower': instance.follower.toJson(),
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'account': instance.account?.toJson(),
|
||||||
|
'tag_id': instance.tagId,
|
||||||
|
'tag': instance.tag?.toJson(),
|
||||||
|
'category_id': instance.categoryId,
|
||||||
|
'category': instance.category?.toJson(),
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/exceptions/request.dart';
|
import 'package:solian/exceptions/request.dart';
|
||||||
import 'package:solian/exceptions/unauthorized.dart';
|
import 'package:solian/exceptions/unauthorized.dart';
|
||||||
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
@ -96,6 +97,15 @@ class PostProvider extends GetConnect {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Post>> listPostFeaturedReply(String alias, {int take = 1}) async {
|
||||||
|
final resp = await get('/posts/$alias/replies/featured?take=$take');
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
throw RequestException(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return List<Post>.from(resp.body.map((x) => Post.fromJson(x)));
|
||||||
|
}
|
||||||
|
|
||||||
Future<Response> getPost(String alias) async {
|
Future<Response> getPost(String alias) async {
|
||||||
final resp = await get('/posts/$alias');
|
final resp = await get('/posts/$alias');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
|
@ -129,6 +129,14 @@ class MessagesFetchingProvider extends GetxController {
|
|||||||
return await receiveEvent(remoteRecord);
|
return await receiveEvent(remoteRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<LocalMessageEventTableData?> getEventFromLocal(int id) async {
|
||||||
|
final database = Get.find<DatabaseProvider>().database;
|
||||||
|
final localRecord = await (database.select(database.localMessageEventTable)
|
||||||
|
..where((x) => x.id.equals(id)))
|
||||||
|
.getSingleOrNull();
|
||||||
|
return localRecord;
|
||||||
|
}
|
||||||
|
|
||||||
/// Pull the remote events to local database
|
/// Pull the remote events to local database
|
||||||
Future<(List<Event>, int)?> pullRemoteEvents(Channel channel,
|
Future<(List<Event>, int)?> pullRemoteEvents(Channel channel,
|
||||||
{String scope = 'global', take = 10, offset = 0}) async {
|
{String scope = 'global', take = 10, offset = 0}) async {
|
||||||
|
@ -1,34 +1,48 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/models/pagination.dart';
|
import 'package:solian/exceptions/request.dart';
|
||||||
import 'package:solian/models/stickers.dart';
|
import 'package:solian/models/stickers.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
class StickerProvider extends GetxController {
|
class StickerProvider extends GetxController {
|
||||||
final RxMap<String, String> aliasImageMapping = RxMap();
|
final RxMap<String, FutureOr<Sticker?>> stickerCache = RxMap();
|
||||||
final RxList<Sticker> availableStickers = RxList.empty(growable: true);
|
|
||||||
|
|
||||||
Future<void> refreshAvailableStickers() async {
|
Future<Sticker?> getStickerByAlias(String alias) {
|
||||||
availableStickers.clear();
|
if (stickerCache.containsKey(alias)) {
|
||||||
aliasImageMapping.clear();
|
return Future.value(stickerCache[alias]);
|
||||||
|
}
|
||||||
|
|
||||||
|
stickerCache[alias] = Future(() async {
|
||||||
|
final client = await ServiceFinder.configureClient('files');
|
||||||
|
final resp = await client.get(
|
||||||
|
'/stickers/lookup/$alias',
|
||||||
|
);
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
if (resp.statusCode == 404) {
|
||||||
|
stickerCache[alias] = null;
|
||||||
|
}
|
||||||
|
throw RequestException(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sticker.fromJson(resp.body);
|
||||||
|
}).then((result) {
|
||||||
|
stickerCache[alias] = result;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Future.value(stickerCache[alias]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Sticker>> searchStickerByAlias(String alias) async {
|
||||||
final client = await ServiceFinder.configureClient('files');
|
final client = await ServiceFinder.configureClient('files');
|
||||||
final resp = await client.get(
|
final resp = await client.get(
|
||||||
'/stickers/manifest?take=100',
|
'/stickers/lookup?probe=$alias',
|
||||||
);
|
);
|
||||||
if (resp.statusCode == 200) {
|
if (resp.statusCode != 200) {
|
||||||
final result = PaginationResult.fromJson(resp.body);
|
throw RequestException(resp);
|
||||||
final out = result.data?.map((e) => StickerPack.fromJson(e)).toList();
|
|
||||||
if (out == null) return;
|
|
||||||
|
|
||||||
for (final pack in out) {
|
|
||||||
for (final sticker in (pack.stickers ?? List<Sticker>.empty())) {
|
|
||||||
sticker.pack = pack;
|
|
||||||
aliasImageMapping[sticker.textPlaceholder.toUpperCase()] =
|
|
||||||
sticker.imageUrl;
|
|
||||||
availableStickers.add(sticker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
availableStickers.refresh();
|
|
||||||
|
return List<Sticker>.from(resp.body.map((x) => Sticker.fromJson(x)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
lib/providers/subscription.dart
Normal file
46
lib/providers/subscription.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
|
import 'package:solian/exceptions/unauthorized.dart';
|
||||||
|
import 'package:solian/models/subscription.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
|
||||||
|
class SubscriptionProvider extends GetxController {
|
||||||
|
Future<Subscription?> getSubscriptionOnUser(int userId) async {
|
||||||
|
final auth = Get.find<AuthProvider>();
|
||||||
|
if (!auth.isAuthorized.value) throw const UnauthorizedException();
|
||||||
|
|
||||||
|
final client = await auth.configureClient('co');
|
||||||
|
final resp = await client.get('/subscriptions/users/$userId');
|
||||||
|
if (resp.statusCode == 404) {
|
||||||
|
return null;
|
||||||
|
} else if (resp.statusCode != 200) {
|
||||||
|
throw RequestException(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Subscription.fromJson(resp.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Subscription> subscribeToUser(int userId) async {
|
||||||
|
final auth = Get.find<AuthProvider>();
|
||||||
|
if (!auth.isAuthorized.value) throw const UnauthorizedException();
|
||||||
|
|
||||||
|
final client = await auth.configureClient('co');
|
||||||
|
final resp = await client.post('/subscriptions/users/$userId', {});
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
throw RequestException(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Subscription.fromJson(resp.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unsubscribeFromUser(int userId) async {
|
||||||
|
final auth = Get.find<AuthProvider>();
|
||||||
|
if (!auth.isAuthorized.value) throw const UnauthorizedException();
|
||||||
|
|
||||||
|
final client = await auth.configureClient('co');
|
||||||
|
final resp = await client.delete('/subscriptions/users/$userId');
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
throw RequestException(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,9 @@ import 'package:solian/models/realm.dart';
|
|||||||
import 'package:solian/screens/about.dart';
|
import 'package:solian/screens/about.dart';
|
||||||
import 'package:solian/screens/account.dart';
|
import 'package:solian/screens/account.dart';
|
||||||
import 'package:solian/screens/account/friend.dart';
|
import 'package:solian/screens/account/friend.dart';
|
||||||
import 'package:solian/screens/account/personalize.dart';
|
import 'package:solian/screens/account/preferences/notifications.dart';
|
||||||
|
import 'package:solian/screens/account/profile_edit.dart';
|
||||||
import 'package:solian/screens/account/profile_page.dart';
|
import 'package:solian/screens/account/profile_page.dart';
|
||||||
import 'package:solian/screens/account/stickers.dart';
|
|
||||||
import 'package:solian/screens/auth/signin.dart';
|
import 'package:solian/screens/auth/signin.dart';
|
||||||
import 'package:solian/screens/auth/signup.dart';
|
import 'package:solian/screens/auth/signup.dart';
|
||||||
import 'package:solian/screens/channel/channel_chat.dart';
|
import 'package:solian/screens/channel/channel_chat.dart';
|
||||||
@ -238,14 +238,6 @@ abstract class AppRouter {
|
|||||||
name: 'accountFriend',
|
name: 'accountFriend',
|
||||||
builder: (context, state) => const FriendScreen(),
|
builder: (context, state) => const FriendScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/account/stickers',
|
|
||||||
name: 'accountStickers',
|
|
||||||
builder: (context, state) => TitleShell(
|
|
||||||
state: state,
|
|
||||||
child: const StickerScreen(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/personalize',
|
path: '/account/personalize',
|
||||||
name: 'accountProfile',
|
name: 'accountProfile',
|
||||||
@ -254,6 +246,14 @@ abstract class AppRouter {
|
|||||||
child: const PersonalizeScreen(),
|
child: const PersonalizeScreen(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/preferences/notifications',
|
||||||
|
name: 'notificationPreferences',
|
||||||
|
builder: (context, state) => TitleShell(
|
||||||
|
state: state,
|
||||||
|
child: const NotificationPreferencesScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/view/:name',
|
path: '/account/view/:name',
|
||||||
name: 'accountProfilePage',
|
name: 'accountProfilePage',
|
||||||
|
@ -45,11 +45,6 @@ class _AccountScreenState extends State<AccountScreen> {
|
|||||||
'accountFriend'.tr,
|
'accountFriend'.tr,
|
||||||
'accountFriend',
|
'accountFriend',
|
||||||
),
|
),
|
||||||
(
|
|
||||||
const Icon(Icons.emoji_symbols),
|
|
||||||
'accountStickers'.tr,
|
|
||||||
'accountStickers',
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
@ -136,6 +131,15 @@ class _AccountScreenState extends State<AccountScreen> {
|
|||||||
AppRouter.instance.pushNamed('settings');
|
AppRouter.instance.pushNamed('settings');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (auth.isAuthorized.value)
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
|
||||||
|
leading: const Icon(Icons.edit_notifications),
|
||||||
|
title: Text('notificationPreferences'.tr),
|
||||||
|
onTap: () {
|
||||||
|
AppRouter.instance.pushNamed('notificationPreferences');
|
||||||
|
},
|
||||||
|
),
|
||||||
const Divider(thickness: 0.3, height: 1)
|
const Divider(thickness: 0.3, height: 1)
|
||||||
.paddingSymmetric(vertical: 4),
|
.paddingSymmetric(vertical: 4),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
118
lib/screens/account/preferences/notifications.dart
Normal file
118
lib/screens/account/preferences/notifications.dart
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get/get_connect/http/src/exceptions/exceptions.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
|
||||||
|
class NotificationPreferencesScreen extends StatefulWidget {
|
||||||
|
const NotificationPreferencesScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NotificationPreferencesScreen> createState() =>
|
||||||
|
_NotificationPreferencesScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NotificationPreferencesScreenState
|
||||||
|
extends State<NotificationPreferencesScreen> {
|
||||||
|
bool _isBusy = true;
|
||||||
|
|
||||||
|
Map<String, bool> _config = {};
|
||||||
|
|
||||||
|
final Map<String, String> _topicMap = {
|
||||||
|
'interactive.feedback': 'notificationTopicPostFeedback'.tr,
|
||||||
|
'interactive.subscription': 'notificationTopicPostSubscription'.tr,
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<void> _getPreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final auth = Get.find<AuthProvider>();
|
||||||
|
if (!auth.isAuthorized.value) throw UnauthorizedException();
|
||||||
|
|
||||||
|
final client = await auth.configureClient('id');
|
||||||
|
final resp = await client.get('/preferences/notifications');
|
||||||
|
if (resp.statusCode != 200 && resp.statusCode != 404) {
|
||||||
|
context.showErrorDialog(RequestException(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.statusCode == 200) {
|
||||||
|
_config = resp.body['config']
|
||||||
|
.map((k, v) => MapEntry(k, v as bool))
|
||||||
|
.cast<String, bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _savePreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final auth = Get.find<AuthProvider>();
|
||||||
|
if (!auth.isAuthorized.value) throw UnauthorizedException();
|
||||||
|
|
||||||
|
final client = await auth.configureClient('id');
|
||||||
|
final resp = await client.put('/preferences/notifications', {
|
||||||
|
'config': _config,
|
||||||
|
});
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
context.showErrorDialog(RequestException(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.showSnackbar('preferencesApplied'.tr);
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
||||||
|
ListTile(
|
||||||
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Icons.save),
|
||||||
|
title: Text('save'.tr),
|
||||||
|
enabled: !_isBusy,
|
||||||
|
onTap: () {
|
||||||
|
_savePreferences();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _topicMap.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final element = _topicMap.entries.elementAt(index);
|
||||||
|
return CheckboxListTile(
|
||||||
|
title: Text(element.value),
|
||||||
|
subtitle: Text(
|
||||||
|
element.key,
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
|
),
|
||||||
|
value: _config[element.key] ?? true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_config[element.key] = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,10 @@ import 'package:solian/models/account.dart';
|
|||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/models/pagination.dart';
|
import 'package:solian/models/pagination.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
|
import 'package:solian/models/subscription.dart';
|
||||||
import 'package:solian/providers/account_status.dart';
|
import 'package:solian/providers/account_status.dart';
|
||||||
import 'package:solian/providers/relation.dart';
|
import 'package:solian/providers/relation.dart';
|
||||||
|
import 'package:solian/providers/subscription.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/account/account_avatar.dart';
|
import 'package:solian/widgets/account/account_avatar.dart';
|
||||||
@ -37,12 +39,21 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
|
|
||||||
bool _isBusy = true;
|
bool _isBusy = true;
|
||||||
bool _isMakingFriend = false;
|
bool _isMakingFriend = false;
|
||||||
|
bool _isSubscribing = false;
|
||||||
bool _showMature = false;
|
bool _showMature = false;
|
||||||
|
|
||||||
Account? _userinfo;
|
Account? _userinfo;
|
||||||
|
Subscription? _subscription;
|
||||||
List<Post> _pinnedPosts = List.empty();
|
List<Post> _pinnedPosts = List.empty();
|
||||||
int _totalUpvote = 0, _totalDownvote = 0;
|
int _totalUpvote = 0, _totalDownvote = 0;
|
||||||
|
|
||||||
|
Future<void> _getSubscription() async {
|
||||||
|
setState(() => _isSubscribing = true);
|
||||||
|
_subscription = await Get.find<SubscriptionProvider>()
|
||||||
|
.getSubscriptionOnUser(_userinfo!.id);
|
||||||
|
setState(() => _isSubscribing = false);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _getUserinfo() async {
|
Future<void> _getUserinfo() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@ -70,7 +81,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getPinnedPosts() async {
|
Future<void> _getPinnedPosts() async {
|
||||||
final client = await ServiceFinder.configureClient('interactive');
|
final client = await ServiceFinder.configureClient('interactive');
|
||||||
final resp = await client.get('/users/${widget.name}/pin');
|
final resp = await client.get('/users/${widget.name}/pin');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
@ -115,8 +126,10 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_getUserinfo();
|
_getUserinfo().then((_) {
|
||||||
getPinnedPosts();
|
_getSubscription();
|
||||||
|
_getPinnedPosts();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatisticsEntry(String label, String content) {
|
Widget _buildStatisticsEntry(String label, String content) {
|
||||||
@ -180,6 +193,40 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_userinfo != null && _subscription == null)
|
||||||
|
OutlinedButton(
|
||||||
|
style: const ButtonStyle(
|
||||||
|
visualDensity:
|
||||||
|
VisualDensity(horizontal: -4, vertical: -2),
|
||||||
|
),
|
||||||
|
onPressed: _isSubscribing
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
setState(() => _isSubscribing = true);
|
||||||
|
_subscription =
|
||||||
|
await Get.find<SubscriptionProvider>()
|
||||||
|
.subscribeToUser(_userinfo!.id);
|
||||||
|
setState(() => _isSubscribing = false);
|
||||||
|
},
|
||||||
|
child: Text('subscribe'.tr),
|
||||||
|
)
|
||||||
|
else if (_userinfo != null)
|
||||||
|
OutlinedButton(
|
||||||
|
style: const ButtonStyle(
|
||||||
|
visualDensity:
|
||||||
|
VisualDensity(horizontal: -4, vertical: -2),
|
||||||
|
),
|
||||||
|
onPressed: _isSubscribing
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
setState(() => _isSubscribing = true);
|
||||||
|
await Get.find<SubscriptionProvider>()
|
||||||
|
.unsubscribeFromUser(_userinfo!.id);
|
||||||
|
_subscription = null;
|
||||||
|
setState(() => _isSubscribing = false);
|
||||||
|
},
|
||||||
|
child: Text('unsubscribe'.tr),
|
||||||
|
),
|
||||||
if (_userinfo != null &&
|
if (_userinfo != null &&
|
||||||
!_relationshipProvider.hasFriend(_userinfo!))
|
!_relationshipProvider.hasFriend(_userinfo!))
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -245,7 +292,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
RefreshIndicator(
|
RefreshIndicator(
|
||||||
onRefresh: () => Future.wait([
|
onRefresh: () => Future.wait([
|
||||||
_postController.reloadAllOver(),
|
_postController.reloadAllOver(),
|
||||||
getPinnedPosts(),
|
_getPinnedPosts(),
|
||||||
]),
|
]),
|
||||||
child: CustomScrollView(slivers: [
|
child: CustomScrollView(slivers: [
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
@ -302,6 +349,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
isClickable: true,
|
isClickable: true,
|
||||||
isNestedClickable: true,
|
isNestedClickable: true,
|
||||||
isShowEmbed: true,
|
isShowEmbed: true,
|
||||||
|
showFeaturedReply: true,
|
||||||
onUpdate: () {
|
onUpdate: () {
|
||||||
_postController.reloadAllOver();
|
_postController.reloadAllOver();
|
||||||
},
|
},
|
||||||
@ -352,7 +400,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
child: AttachmentListEntry(
|
child: AttachmentListEntry(
|
||||||
item: item,
|
item: item,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
parentId: 'album',
|
parentId: 'album-$index',
|
||||||
showMature: _showMature,
|
showMature: _showMature,
|
||||||
onReveal: (value) {
|
onReveal: (value) {
|
||||||
setState(() => _showMature = value);
|
setState(() => _showMature = value);
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
|
||||||
import 'package:solian/models/pagination.dart';
|
|
||||||
import 'package:solian/models/stickers.dart';
|
|
||||||
import 'package:solian/providers/auth.dart';
|
|
||||||
import 'package:solian/providers/stickers.dart';
|
|
||||||
import 'package:solian/services.dart';
|
|
||||||
import 'package:solian/widgets/auto_cache_image.dart';
|
|
||||||
import 'package:solian/widgets/stickers/sticker_uploader.dart';
|
|
||||||
|
|
||||||
class StickerScreen extends StatefulWidget {
|
|
||||||
const StickerScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StickerScreen> createState() => _StickerScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StickerScreenState extends State<StickerScreen> {
|
|
||||||
final PagingController<int, StickerPack> _pagingController =
|
|
||||||
PagingController(firstPageKey: 0);
|
|
||||||
|
|
||||||
Future<bool> _promptDelete(Sticker item, String prefix) async {
|
|
||||||
final AuthProvider auth = Get.find();
|
|
||||||
if (auth.isAuthorized.isFalse) return false;
|
|
||||||
|
|
||||||
final confirm = await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text('stickerDeletionConfirm'.tr),
|
|
||||||
content: Text(
|
|
||||||
'stickerDeletionConfirmCaption'.trParams({
|
|
||||||
'name': ':${'$prefix${item.alias}'.camelCase}:',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text('cancel'.tr),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, true),
|
|
||||||
child: Text('confirm'.tr),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (confirm != true) return false;
|
|
||||||
|
|
||||||
final client = await auth.configureClient('files');
|
|
||||||
final resp = await client.delete('/stickers/${item.id}');
|
|
||||||
|
|
||||||
return resp.statusCode == 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool?> _promptUploadSticker({Sticker? edit}) {
|
|
||||||
return showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => StickerUploadDialog(
|
|
||||||
edit: edit,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEmoteEntry(Sticker item, String prefix) {
|
|
||||||
final imageUrl = ServiceFinder.buildUrl(
|
|
||||||
'files',
|
|
||||||
'/attachments/${item.attachment.rid}',
|
|
||||||
);
|
|
||||||
return ListTile(
|
|
||||||
title: Text(item.name),
|
|
||||||
subtitle: Text(item.textWarpedPlaceholder),
|
|
||||||
contentPadding: const EdgeInsets.only(left: 16, right: 14),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.edit_square),
|
|
||||||
onPressed: () {
|
|
||||||
_promptUploadSticker(edit: item).then((value) {
|
|
||||||
if (value == true) _pagingController.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
onPressed: () {
|
|
||||||
_promptDelete(item, prefix).then((value) {
|
|
||||||
if (value == true) _pagingController.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
leading: AutoCacheImage(
|
|
||||||
imageUrl,
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
noErrorWidget: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final AuthProvider auth = Get.find();
|
|
||||||
final name = auth.userProfile.value!['name'];
|
|
||||||
_pagingController.addPageRequestListener((pageKey) async {
|
|
||||||
final client = await ServiceFinder.configureClient('files');
|
|
||||||
final resp = await client.get(
|
|
||||||
'/stickers/manifest?take=10&offset=$pageKey&author=$name',
|
|
||||||
);
|
|
||||||
if (resp.statusCode == 200) {
|
|
||||||
final result = PaginationResult.fromJson(resp.body);
|
|
||||||
final out = result.data?.map((e) => StickerPack.fromJson(e)).toList();
|
|
||||||
if (out != null && result.data!.length >= 10) {
|
|
||||||
_pagingController.appendPage(out, pageKey + out.length);
|
|
||||||
} else if (out != null) {
|
|
||||||
_pagingController.appendLastPage(out);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_pagingController.error = resp.bodyString;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
final StickerProvider sticker = Get.find();
|
|
||||||
sticker.refreshAvailableStickers();
|
|
||||||
_pagingController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
onPressed: () {
|
|
||||||
_promptUploadSticker().then((value) {
|
|
||||||
if (value == true) _pagingController.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
body: RefreshIndicator(
|
|
||||||
onRefresh: () => Future.sync(() => _pagingController.refresh()),
|
|
||||||
child: CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
PagedSliverList<int, StickerPack>(
|
|
||||||
pagingController: _pagingController,
|
|
||||||
builderDelegate: PagedChildBuilderDelegate(
|
|
||||||
itemBuilder: (BuildContext context, item, int index) {
|
|
||||||
return ExpansionTile(
|
|
||||||
title: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(item.name),
|
|
||||||
const Gap(6),
|
|
||||||
Badge(
|
|
||||||
label: Text('#${item.id}'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
item.description,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
children: item.stickers?.map((x) {
|
|
||||||
x.pack = item;
|
|
||||||
return _buildEmoteEntry(x, item.prefix);
|
|
||||||
}).toList() ??
|
|
||||||
List.empty(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,9 @@ import 'package:solian/exceptions/request.dart';
|
|||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
import 'package:solian/models/auth.dart';
|
import 'package:solian/models/auth.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/providers/content/channel.dart';
|
||||||
|
import 'package:solian/providers/content/realm.dart';
|
||||||
|
import 'package:solian/providers/relation.dart';
|
||||||
import 'package:solian/providers/websocket.dart';
|
import 'package:solian/providers/websocket.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/widgets/sized_container.dart';
|
import 'package:solian/widgets/sized_container.dart';
|
||||||
@ -153,7 +156,7 @@ class _SignInScreenState extends State<SignInScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Check ticket
|
// Check ticket
|
||||||
final resp = await client.patch('/auth', {
|
final resp = await client.request('/auth', 'PATCH', body: {
|
||||||
'ticket_id': _currentTicket!.id,
|
'ticket_id': _currentTicket!.id,
|
||||||
'factor_id': _factorPicked!,
|
'factor_id': _factorPicked!,
|
||||||
'code': password,
|
'code': password,
|
||||||
@ -164,6 +167,7 @@ class _SignInScreenState extends State<SignInScreen> {
|
|||||||
|
|
||||||
final result = AuthResult.fromJson(resp.body);
|
final result = AuthResult.fromJson(resp.body);
|
||||||
_currentTicket = result.ticket;
|
_currentTicket = result.ticket;
|
||||||
|
_passwordController.clear();
|
||||||
|
|
||||||
// Finish sign in if possible
|
// Finish sign in if possible
|
||||||
if (result.isFinished) {
|
if (result.isFinished) {
|
||||||
@ -173,6 +177,9 @@ class _SignInScreenState extends State<SignInScreen> {
|
|||||||
await auth.refreshAuthorizeStatus();
|
await auth.refreshAuthorizeStatus();
|
||||||
await auth.refreshUserProfile();
|
await auth.refreshUserProfile();
|
||||||
|
|
||||||
|
Get.find<ChannelProvider>().refreshAvailableChannel();
|
||||||
|
Get.find<RealmProvider>().refreshAvailableRealms();
|
||||||
|
Get.find<RelationshipProvider>().refreshRelativeList();
|
||||||
Get.find<WebSocketProvider>().registerPushNotifications();
|
Get.find<WebSocketProvider>().registerPushNotifications();
|
||||||
autoConfigureBackgroundNotificationService();
|
autoConfigureBackgroundNotificationService();
|
||||||
autoStartBackgroundNotificationService();
|
autoStartBackgroundNotificationService();
|
||||||
|
@ -114,7 +114,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.settings),
|
leading: const Icon(Icons.settings),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('channelSettings'.tr.capitalize!),
|
title: Text('channelSettings'.tr),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
AppRouter.instance
|
AppRouter.instance
|
||||||
.pushNamed(
|
.pushNamed(
|
||||||
@ -173,7 +173,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.notifications_active),
|
leading: const Icon(Icons.notifications_active),
|
||||||
title: Text('channelNotifyLevel'.tr.capitalize!),
|
title: Text('channelNotifyLevel'.tr),
|
||||||
trailing: DropdownButtonHideUnderline(
|
trailing: DropdownButtonHideUnderline(
|
||||||
child: DropdownButton2<int>(
|
child: DropdownButton2<int>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
@ -208,7 +208,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.supervisor_account),
|
leading: const Icon(Icons.supervisor_account),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('channelMembers'.tr.capitalize!),
|
title: Text('channelMembers'.tr),
|
||||||
onTap: () => showMemberList(),
|
onTap: () => showMemberList(),
|
||||||
),
|
),
|
||||||
...(_isOwned ? ownerActions : List.empty()),
|
...(_isOwned ? ownerActions : List.empty()),
|
||||||
|
@ -35,13 +35,14 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
final _descriptionController = TextEditingController();
|
final _descriptionController = TextEditingController();
|
||||||
|
|
||||||
bool _isEncrypted = false;
|
bool _isPublic = false;
|
||||||
|
bool _isCommunity = false;
|
||||||
|
|
||||||
void applyChannel() async {
|
void _applyChannel() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) return;
|
if (auth.isAuthorized.isFalse) return;
|
||||||
|
|
||||||
if (_aliasController.value.text.isEmpty) randomizeAlias();
|
if (_aliasController.value.text.isEmpty) _randomizeAlias();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
'alias': _aliasController.value.text.toLowerCase(),
|
'alias': _aliasController.value.text.toLowerCase(),
|
||||||
'name': _nameController.value.text,
|
'name': _nameController.value.text,
|
||||||
'description': _descriptionController.value.text,
|
'description': _descriptionController.value.text,
|
||||||
'is_encrypted': _isEncrypted,
|
'is_encrypted': _isPublic,
|
||||||
};
|
};
|
||||||
|
|
||||||
Response? resp;
|
Response? resp;
|
||||||
@ -71,35 +72,44 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void randomizeAlias() {
|
void _randomizeAlias() {
|
||||||
_aliasController.text =
|
_aliasController.text =
|
||||||
const Uuid().v4().replaceAll('-', '').substring(0, 12);
|
const Uuid().v4().replaceAll('-', '').substring(0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncWidget() {
|
void _syncWidget() {
|
||||||
if (widget.edit != null) {
|
if (widget.edit != null) {
|
||||||
_aliasController.text = widget.edit!.alias;
|
_aliasController.text = widget.edit!.alias;
|
||||||
_nameController.text = widget.edit!.name;
|
_nameController.text = widget.edit!.name;
|
||||||
_descriptionController.text = widget.edit!.description;
|
_descriptionController.text = widget.edit!.description;
|
||||||
_isEncrypted = widget.edit!.isEncrypted;
|
_isPublic = widget.edit!.isPublic;
|
||||||
|
_isCommunity = widget.edit!.isCommunity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelAction() {
|
void _cancelAction() {
|
||||||
AppRouter.instance.pop();
|
AppRouter.instance.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
syncWidget();
|
_syncWidget();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_aliasController.dispose();
|
||||||
|
_nameController.dispose();
|
||||||
|
_descriptionController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final notifyBannerActions = [
|
final notifyBannerActions = [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: cancelAction,
|
onPressed: _cancelAction,
|
||||||
child: Text('cancel'.tr),
|
child: Text('cancel'.tr),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -113,7 +123,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isBusy ? null : () => applyChannel(),
|
onPressed: _isBusy ? null : () => _applyChannel(),
|
||||||
child: Text('apply'.tr.toUpperCase()),
|
child: Text('apply'.tr.toUpperCase()),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -164,7 +174,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
visualDensity:
|
visualDensity:
|
||||||
const VisualDensity(horizontal: -2, vertical: -2),
|
const VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () => randomizeAlias(),
|
onPressed: () => _randomizeAlias(),
|
||||||
child: const Icon(Icons.refresh),
|
child: const Icon(Icons.refresh),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -196,12 +206,17 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
),
|
),
|
||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
title: Text('channelEncrypted'.tr),
|
title: Text('channelPublic'.tr),
|
||||||
value: _isEncrypted,
|
value: _isPublic,
|
||||||
onChanged: (widget.edit?.isEncrypted ?? false)
|
onChanged: (value) =>
|
||||||
? null
|
setState(() => _isPublic = value ?? false),
|
||||||
: (newValue) =>
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
setState(() => _isEncrypted = newValue ?? false),
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: Text('channelCommunity'.tr),
|
||||||
|
value: _isCommunity,
|
||||||
|
onChanged: (value) =>
|
||||||
|
setState(() => _isCommunity = value ?? false),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -125,7 +125,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
child: Obx(
|
child: Obx(
|
||||||
() => ChannelListWidget(
|
() => ChannelListWidget(
|
||||||
noCategory: true,
|
noCategory: true,
|
||||||
channels: _channels.directChannels,
|
channels: List.from([
|
||||||
|
..._channels.groupChannels
|
||||||
|
.where((x) => x.realmId == null),
|
||||||
|
..._channels.directChannels
|
||||||
|
]),
|
||||||
selfId: selfId,
|
selfId: selfId,
|
||||||
useReplace: true,
|
useReplace: true,
|
||||||
),
|
),
|
||||||
|
@ -379,6 +379,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
isClickable: true,
|
isClickable: true,
|
||||||
isShowEmbed: true,
|
isShowEmbed: true,
|
||||||
isNestedClickable: true,
|
isNestedClickable: true,
|
||||||
|
showFeaturedReply: true,
|
||||||
onUpdate: (_) {
|
onUpdate: (_) {
|
||||||
_pullPosts();
|
_pullPosts();
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/providers/content/posts.dart';
|
import 'package:solian/providers/content/posts.dart';
|
||||||
|
import 'package:solian/providers/last_read.dart';
|
||||||
import 'package:solian/widgets/posts/post_item.dart';
|
import 'package:solian/widgets/posts/post_item.dart';
|
||||||
import 'package:solian/widgets/posts/post_replies.dart';
|
import 'package:solian/widgets/posts/post_replies.dart';
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
Future<Post?> getDetail() async {
|
Future<Post?> getDetail() async {
|
||||||
if (widget.post != null) {
|
if (widget.post != null) {
|
||||||
item = widget.post;
|
item = widget.post;
|
||||||
|
Get.find<LastReadProvider>().feedLastReadAt = item?.id;
|
||||||
return widget.post;
|
return widget.post;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +40,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
context.showErrorDialog(e).then((_) => Navigator.pop(context));
|
context.showErrorDialog(e).then((_) => Navigator.pop(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Get.find<LastReadProvider>().feedLastReadAt = item?.id;
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,13 @@ import 'package:solian/providers/auth.dart';
|
|||||||
import 'package:solian/providers/content/realm.dart';
|
import 'package:solian/providers/content/realm.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/screens/account/notification.dart';
|
import 'package:solian/screens/account/notification.dart';
|
||||||
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
|
import 'package:solian/widgets/account/account_avatar.dart';
|
||||||
import 'package:solian/widgets/account/signin_required_overlay.dart';
|
import 'package:solian/widgets/account/signin_required_overlay.dart';
|
||||||
import 'package:solian/widgets/app_bar_leading.dart';
|
import 'package:solian/widgets/app_bar_leading.dart';
|
||||||
import 'package:solian/widgets/app_bar_title.dart';
|
import 'package:solian/widgets/app_bar_title.dart';
|
||||||
|
import 'package:solian/widgets/auto_cache_image.dart';
|
||||||
import 'package:solian/widgets/current_state_action.dart';
|
import 'package:solian/widgets/current_state_action.dart';
|
||||||
import 'package:solian/widgets/sized_container.dart';
|
import 'package:solian/widgets/sized_container.dart';
|
||||||
|
|
||||||
@ -128,19 +131,34 @@ class _RealmListScreenState extends State<RealmListScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: (element.banner?.isEmpty ?? true)
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: AutoCacheImage(
|
||||||
|
ServiceFinder.buildUrl(
|
||||||
|
'uc',
|
||||||
|
'/attachments/${element.banner}',
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Positioned(
|
Positioned(
|
||||||
bottom: -30,
|
bottom: -30,
|
||||||
left: 18,
|
left: 18,
|
||||||
child: CircleAvatar(
|
child: (element.avatar?.isEmpty ?? true)
|
||||||
radius: 24,
|
? CircleAvatar(
|
||||||
backgroundColor: Colors.indigo,
|
radius: 24,
|
||||||
child: FaIcon(
|
backgroundColor:
|
||||||
FontAwesomeIcons.globe,
|
Theme.of(context).colorScheme.primary,
|
||||||
color: Colors.white,
|
child: const FaIcon(
|
||||||
size: 18,
|
FontAwesomeIcons.globe,
|
||||||
),
|
color: Colors.white,
|
||||||
),
|
size: 18,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: AccountAvatar(
|
||||||
|
content: element.avatar!,
|
||||||
|
bgColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -69,7 +69,8 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.settings),
|
leading: const Icon(Icons.settings),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('realmSettings'.tr.capitalize!),
|
title: Text('realmSettings'.tr),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
AppRouter.instance
|
AppRouter.instance
|
||||||
.pushNamed(
|
.pushNamed(
|
||||||
@ -120,14 +121,16 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Icons.supervisor_account),
|
leading: const Icon(Icons.supervisor_account),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('realmMembers'.tr.capitalize!),
|
title: Text('realmMembers'.tr),
|
||||||
onTap: () => showMemberList(),
|
onTap: () => showMemberList(),
|
||||||
),
|
),
|
||||||
...(_isOwned ? ownerActions : List.empty()),
|
...(_isOwned ? ownerActions : List.empty()),
|
||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: _isOwned
|
leading: _isOwned
|
||||||
? const Icon(Icons.delete)
|
? const Icon(Icons.delete)
|
||||||
: const Icon(Icons.exit_to_app),
|
: const Icon(Icons.exit_to_app),
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_cropper/image_cropper.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/models/realm.dart';
|
import 'package:solian/models/realm.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/providers/content/attachment.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/app_bar_leading.dart';
|
import 'package:solian/widgets/app_bar_leading.dart';
|
||||||
@ -29,17 +35,19 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
final _aliasController = TextEditingController();
|
final _aliasController = TextEditingController();
|
||||||
|
final _avatarController = TextEditingController();
|
||||||
|
final _bannerController = TextEditingController();
|
||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
final _descriptionController = TextEditingController();
|
final _descriptionController = TextEditingController();
|
||||||
|
|
||||||
bool _isCommunity = false;
|
bool _isCommunity = false;
|
||||||
bool _isPublic = false;
|
bool _isPublic = false;
|
||||||
|
|
||||||
void applyRealm() async {
|
void _applyRealm() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) return;
|
if (auth.isAuthorized.isFalse) return;
|
||||||
|
|
||||||
if (_aliasController.value.text.isEmpty) randomizeAlias();
|
if (_aliasController.value.text.isEmpty) _randomizeAlias();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@ -49,6 +57,8 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
'alias': _aliasController.value.text.toLowerCase(),
|
'alias': _aliasController.value.text.toLowerCase(),
|
||||||
'name': _nameController.value.text,
|
'name': _nameController.value.text,
|
||||||
'description': _descriptionController.value.text,
|
'description': _descriptionController.value.text,
|
||||||
|
'avatar': _avatarController.value.text,
|
||||||
|
'banner': _bannerController.value.text,
|
||||||
'is_public': _isPublic,
|
'is_public': _isPublic,
|
||||||
'is_community': _isCommunity,
|
'is_community': _isCommunity,
|
||||||
};
|
};
|
||||||
@ -68,31 +78,110 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void randomizeAlias() {
|
final _imagePicker = ImagePicker();
|
||||||
|
|
||||||
|
Future<void> _editImage(String position) async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (auth.isAuthorized.isFalse) return;
|
||||||
|
|
||||||
|
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (image == null) return;
|
||||||
|
|
||||||
|
CroppedFile? croppedFile = await ImageCropper().cropImage(
|
||||||
|
sourcePath: image.path,
|
||||||
|
uiSettings: [
|
||||||
|
AndroidUiSettings(
|
||||||
|
toolbarTitle: 'cropImage'.tr,
|
||||||
|
toolbarColor: Theme.of(context).colorScheme.primary,
|
||||||
|
toolbarWidgetColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
aspectRatioPresets: [
|
||||||
|
if (position == 'avatar') CropAspectRatioPreset.square,
|
||||||
|
if (position == 'banner') _BannerCropAspectRatioPreset(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IOSUiSettings(
|
||||||
|
title: 'cropImage'.tr,
|
||||||
|
aspectRatioPresets: [
|
||||||
|
if (position == 'avatar') CropAspectRatioPreset.square,
|
||||||
|
if (position == 'banner') _BannerCropAspectRatioPreset(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
WebUiSettings(
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (croppedFile == null) return;
|
||||||
|
final file = File(croppedFile.path);
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final AttachmentProvider attach = Get.find();
|
||||||
|
|
||||||
|
Attachment? attachResult;
|
||||||
|
try {
|
||||||
|
attachResult = await attach.createAttachmentDirectly(
|
||||||
|
await file.readAsBytes(),
|
||||||
|
file.path,
|
||||||
|
'avatar',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
context.showErrorDialog(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 'avatar':
|
||||||
|
_avatarController.text = attachResult.rid;
|
||||||
|
break;
|
||||||
|
case 'banner':
|
||||||
|
_bannerController.text = attachResult.rid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _randomizeAlias() {
|
||||||
_aliasController.text =
|
_aliasController.text =
|
||||||
const Uuid().v4().replaceAll('-', '').substring(0, 12);
|
const Uuid().v4().replaceAll('-', '').substring(0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncWidget() {
|
void _syncWidget() {
|
||||||
if (widget.edit != null) {
|
if (widget.edit != null) {
|
||||||
_aliasController.text = widget.edit!.alias;
|
_aliasController.text = widget.edit!.alias;
|
||||||
_nameController.text = widget.edit!.name;
|
_nameController.text = widget.edit!.name;
|
||||||
_descriptionController.text = widget.edit!.description;
|
_descriptionController.text = widget.edit!.description;
|
||||||
|
_avatarController.text = widget.edit!.avatar ?? '';
|
||||||
|
_bannerController.text = widget.edit!.banner ?? '';
|
||||||
_isPublic = widget.edit!.isPublic;
|
_isPublic = widget.edit!.isPublic;
|
||||||
_isCommunity = widget.edit!.isCommunity;
|
_isCommunity = widget.edit!.isCommunity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelAction() {
|
void _cancelAction() {
|
||||||
AppRouter.instance.pop();
|
AppRouter.instance.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
syncWidget();
|
_syncWidget();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_aliasController.dispose();
|
||||||
|
_avatarController.dispose();
|
||||||
|
_bannerController.dispose();
|
||||||
|
_nameController.dispose();
|
||||||
|
_descriptionController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
@ -105,7 +194,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isBusy ? null : () => applyRealm(),
|
onPressed: _isBusy ? null : () => _applyRealm(),
|
||||||
child: Text('apply'.tr.toUpperCase()),
|
child: Text('apply'.tr.toUpperCase()),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -126,7 +215,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: cancelAction,
|
onPressed: _cancelAction,
|
||||||
child: Text('cancel'.tr),
|
child: Text('cancel'.tr),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -150,7 +239,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
visualDensity:
|
visualDensity:
|
||||||
const VisualDensity(horizontal: -2, vertical: -2),
|
const VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () => randomizeAlias(),
|
onPressed: () => _randomizeAlias(),
|
||||||
child: const Icon(Icons.refresh),
|
child: const Icon(Icons.refresh),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -166,6 +255,55 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).paddingSymmetric(horizontal: 16, vertical: 8),
|
).paddingSymmetric(horizontal: 16, vertical: 8),
|
||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _avatarController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'realmAvatar'.tr,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -2, vertical: -2),
|
||||||
|
),
|
||||||
|
onPressed: _isBusy ? null : () => _editImage('avatar'),
|
||||||
|
child: const Icon(Icons.upload),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
).paddingSymmetric(horizontal: 16, vertical: 2),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _bannerController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'realmBanner'.tr,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -2, vertical: -2),
|
||||||
|
),
|
||||||
|
onPressed: _isBusy ? null : () => _editImage('banner'),
|
||||||
|
child: const Icon(Icons.upload),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
).paddingSymmetric(horizontal: 16, vertical: 2),
|
||||||
|
const Divider(thickness: 0.3),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
minLines: 5,
|
minLines: 5,
|
||||||
@ -202,3 +340,11 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BannerCropAspectRatioPreset extends CropAspectRatioPresetData {
|
||||||
|
@override
|
||||||
|
(int, int)? get data => (16, 7);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => '16x7';
|
||||||
|
}
|
||||||
|
@ -93,14 +93,14 @@ class _AttachmentEditorThumbnailDialogState
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('postThumbnail'.tr),
|
title: Text('attachmentThumbnail'.tr),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text('postThumbnailAttachmentNew'.tr),
|
title: Text('attachmentThumbnailAttachmentNew'.tr),
|
||||||
contentPadding: const EdgeInsets.only(left: 12, right: 9),
|
contentPadding: const EdgeInsets.only(left: 12, right: 9),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
@ -122,7 +122,7 @@ class _AttachmentEditorThumbnailDialogState
|
|||||||
isDense: true,
|
isDense: true,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixText: '#',
|
prefixText: '#',
|
||||||
labelText: 'postThumbnailAttachment'.tr,
|
labelText: 'attachmentThumbnailAttachment'.tr,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
|
@ -282,6 +282,8 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.item.alt,
|
widget.item.alt,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@ -447,6 +449,8 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.item.alt,
|
widget.item.alt,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/controllers/chat_events_controller.dart';
|
import 'package:solian/controllers/chat_events_controller.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
@ -213,6 +216,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
|
|||||||
return _buildEntry(element);
|
return _buildEntry(element);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SliverGap(max(16, MediaQuery.of(context).padding.bottom)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -92,10 +92,13 @@ class ChatEventList extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
onFetchData: () {
|
onFetchData: () {
|
||||||
chatController.loadEvents(
|
if (chatController.currentEvents.length <
|
||||||
chatController.channel!,
|
chatController.totalEvents.value) {
|
||||||
chatController.scope!,
|
chatController.loadEvents(
|
||||||
);
|
chatController.channel!,
|
||||||
|
chatController.scope!,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@ -405,12 +405,9 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
if (emojiMatch != null) {
|
if (emojiMatch != null) {
|
||||||
final StickerProvider stickers = Get.find();
|
final StickerProvider stickers = Get.find();
|
||||||
final emoteSearch = emojiMatch[2]!;
|
final emoteSearch = emojiMatch[2]!;
|
||||||
return stickers.availableStickers
|
final result = await stickers
|
||||||
.where(
|
.searchStickerByAlias(emoteSearch.substring(1));
|
||||||
(x) => x.textWarpedPlaceholder
|
return result
|
||||||
.toUpperCase()
|
|
||||||
.contains(emoteSearch.toUpperCase()),
|
|
||||||
)
|
|
||||||
.map(
|
.map(
|
||||||
(x) => ChatMessageSuggestion(
|
(x) => ChatMessageSuggestion(
|
||||||
type: 'emotes',
|
type: 'emotes',
|
||||||
|
@ -89,7 +89,6 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
case 'stickers':
|
case 'stickers':
|
||||||
double radius = 8;
|
double radius = 8;
|
||||||
final StickerProvider sticker = Get.find();
|
final StickerProvider sticker = Get.find();
|
||||||
url = sticker.aliasImageMapping[segments[1].toUpperCase()]!;
|
|
||||||
if (emojiMatch.length <= 1 && isOnlyEmoji) {
|
if (emojiMatch.length <= 1 && isOnlyEmoji) {
|
||||||
width = 128;
|
width = 128;
|
||||||
height = 128;
|
height = 128;
|
||||||
@ -106,11 +105,20 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.all(Radius.circular(radius)),
|
borderRadius: BorderRadius.all(Radius.circular(radius)),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: AutoCacheImage(
|
child: FutureBuilder(
|
||||||
url,
|
future: sticker.getStickerByAlias(segments[1]),
|
||||||
width: width,
|
builder: (context, snapshot) {
|
||||||
height: height,
|
if (!snapshot.hasData) {
|
||||||
fit: fit,
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
return AutoCacheImage(
|
||||||
|
snapshot.data!.imageUrl,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
fit: fit,
|
||||||
|
noErrorWidget: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).paddingSymmetric(vertical: 4);
|
).paddingSymmetric(vertical: 4);
|
||||||
@ -172,7 +180,8 @@ class _CustomEmoteInlineSyntax extends InlineSyntax {
|
|||||||
bool onMatch(markdown.InlineParser parser, Match match) {
|
bool onMatch(markdown.InlineParser parser, Match match) {
|
||||||
final StickerProvider sticker = Get.find();
|
final StickerProvider sticker = Get.find();
|
||||||
final alias = match[1]!.toUpperCase();
|
final alias = match[1]!.toUpperCase();
|
||||||
if (sticker.aliasImageMapping[alias] == null) {
|
if (sticker.stickerCache.containsKey(alias) &&
|
||||||
|
sticker.stickerCache[alias] == null) {
|
||||||
parser.advanceBy(1);
|
parser.advanceBy(1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/models/realm.dart';
|
import 'package:solian/models/realm.dart';
|
||||||
@ -5,7 +6,9 @@ import 'package:solian/providers/auth.dart';
|
|||||||
import 'package:solian/providers/content/channel.dart';
|
import 'package:solian/providers/content/channel.dart';
|
||||||
import 'package:solian/providers/content/realm.dart';
|
import 'package:solian/providers/content/realm.dart';
|
||||||
import 'package:solian/providers/navigation.dart';
|
import 'package:solian/providers/navigation.dart';
|
||||||
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/widgets/account/account_avatar.dart';
|
import 'package:solian/widgets/account/account_avatar.dart';
|
||||||
|
import 'package:solian/widgets/auto_cache_image.dart';
|
||||||
import 'package:solian/widgets/channel/channel_list.dart';
|
import 'package:solian/widgets/channel/channel_list.dart';
|
||||||
|
|
||||||
class AppNavigationRegion extends StatefulWidget {
|
class AppNavigationRegion extends StatefulWidget {
|
||||||
@ -126,16 +129,12 @@ class _AppNavigationRegionState extends State<AppNavigationRegion> {
|
|||||||
final NavigationStateProvider navState = Get.find();
|
final NavigationStateProvider navState = Get.find();
|
||||||
|
|
||||||
return Obx(
|
return Obx(
|
||||||
() => AnimatedSwitcher(
|
() => PageTransitionSwitcher(
|
||||||
switchInCurve: Curves.fastOutSlowIn,
|
transitionBuilder: (child, animation, secondaryAnimation) {
|
||||||
switchOutCurve: Curves.fastOutSlowIn,
|
return SharedAxisTransition(
|
||||||
duration: const Duration(milliseconds: 300),
|
animation: animation,
|
||||||
transitionBuilder: (child, animation) {
|
secondaryAnimation: secondaryAnimation,
|
||||||
return SlideTransition(
|
transitionType: SharedAxisTransitionType.horizontal,
|
||||||
position: Tween<Offset>(
|
|
||||||
begin: const Offset(1.0, 0.0),
|
|
||||||
end: Offset.zero,
|
|
||||||
).animate(animation),
|
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: child,
|
child: child,
|
||||||
@ -172,6 +171,19 @@ class _AppNavigationRegionState extends State<AppNavigationRegion> {
|
|||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (!widget.isCollapsed &&
|
||||||
|
(navState.focusedRealm.value!.banner?.isNotEmpty ??
|
||||||
|
false))
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 7,
|
||||||
|
child: AutoCacheImage(
|
||||||
|
ServiceFinder.buildUrl(
|
||||||
|
'uc',
|
||||||
|
'/attachments/${navState.focusedRealm.value!.banner}',
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
if (widget.isCollapsed)
|
if (widget.isCollapsed)
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: navState.focusedRealm.value!.name,
|
message: navState.focusedRealm.value!.name,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/controllers/post_editor_controller.dart';
|
import 'package:solian/controllers/post_editor_controller.dart';
|
||||||
import 'package:solian/widgets/attachments/attachment_editor.dart';
|
import 'package:solian/widgets/attachments/attachment_editor.dart';
|
||||||
@ -58,18 +57,25 @@ class _PostEditorThumbnailDialogState extends State<PostEditorThumbnailDialog> {
|
|||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
Card(
|
||||||
title: Text('postThumbnailAttachmentNew'.tr),
|
margin: EdgeInsets.zero,
|
||||||
contentPadding: const EdgeInsets.only(left: 16, right: 13),
|
child: ListTile(
|
||||||
trailing: const Icon(Icons.chevron_right),
|
title: Text('postThumbnailAttachmentNew'.tr),
|
||||||
shape: const RoundedRectangleBorder(
|
contentPadding: const EdgeInsets.only(left: 12, right: 9),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_promptUploadNewAttachment();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
_promptUploadNewAttachment();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Row(children: <Widget>[
|
||||||
|
Expanded(child: Divider()),
|
||||||
|
Text('OR'),
|
||||||
|
Expanded(child: Divider()),
|
||||||
|
]).paddingOnly(top: 12, bottom: 16, left: 16, right: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _attachmentController,
|
controller: _attachmentController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get_utils/get_utils.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
|
import 'package:solian/providers/content/posts.dart';
|
||||||
import 'package:solian/screens/posts/post_detail.dart';
|
import 'package:solian/screens/posts/post_detail.dart';
|
||||||
import 'package:solian/shells/title_shell.dart';
|
import 'package:solian/shells/title_shell.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
@ -30,6 +32,7 @@ class PostItem extends StatefulWidget {
|
|||||||
final bool isFullDate;
|
final bool isFullDate;
|
||||||
final bool isFullContent;
|
final bool isFullContent;
|
||||||
final bool isContentSelectable;
|
final bool isContentSelectable;
|
||||||
|
final bool showFeaturedReply;
|
||||||
final String? attachmentParent;
|
final String? attachmentParent;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
|
||||||
@ -45,6 +48,7 @@ class PostItem extends StatefulWidget {
|
|||||||
this.isFullDate = false,
|
this.isFullDate = false,
|
||||||
this.isFullContent = false,
|
this.isFullContent = false,
|
||||||
this.isContentSelectable = false,
|
this.isContentSelectable = false,
|
||||||
|
this.showFeaturedReply = false,
|
||||||
this.attachmentParent,
|
this.attachmentParent,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
});
|
});
|
||||||
@ -103,7 +107,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
children: [
|
children: [
|
||||||
if (widget.isCompact)
|
if (widget.isCompact)
|
||||||
AccountAvatar(
|
AccountAvatar(
|
||||||
content: item.author.avatar.toString(),
|
content: item.author.avatar,
|
||||||
radius: 10,
|
radius: 10,
|
||||||
).paddingOnly(left: 2, top: 1),
|
).paddingOnly(left: 2, top: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -320,6 +324,102 @@ class _PostItemState extends State<PostItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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
|
||||||
@ -417,7 +517,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: AccountAvatar(content: item.author.avatar.toString()),
|
child: AccountAvatar(content: item.author.avatar),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
@ -506,6 +606,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
left: 16,
|
left: 16,
|
||||||
),
|
),
|
||||||
_buildAttachments(),
|
_buildAttachments(),
|
||||||
|
if (widget.showFeaturedReply) _buildFeaturedReply(),
|
||||||
if (widget.isShowReply || widget.isReactable)
|
if (widget.isShowReply || widget.isReactable)
|
||||||
PostQuickAction(
|
PostQuickAction(
|
||||||
isShowReply: widget.isShowReply,
|
isShowReply: widget.isShowReply,
|
||||||
|
@ -33,6 +33,7 @@ class PostListWidget extends StatelessWidget {
|
|||||||
isShowEmbed: isShowEmbed,
|
isShowEmbed: isShowEmbed,
|
||||||
isNestedClickable: isNestedClickable,
|
isNestedClickable: isNestedClickable,
|
||||||
isClickable: isClickable,
|
isClickable: isClickable,
|
||||||
|
showFeaturedReply: true,
|
||||||
item: item,
|
item: item,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
onUpdate: () {
|
onUpdate: () {
|
||||||
@ -51,6 +52,7 @@ class PostListEntryWidget extends StatelessWidget {
|
|||||||
final bool isShowEmbed;
|
final bool isShowEmbed;
|
||||||
final bool isNestedClickable;
|
final bool isNestedClickable;
|
||||||
final bool isClickable;
|
final bool isClickable;
|
||||||
|
final bool showFeaturedReply;
|
||||||
final Post item;
|
final Post item;
|
||||||
final Function onUpdate;
|
final Function onUpdate;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
@ -61,6 +63,7 @@ class PostListEntryWidget extends StatelessWidget {
|
|||||||
required this.isShowEmbed,
|
required this.isShowEmbed,
|
||||||
required this.isNestedClickable,
|
required this.isNestedClickable,
|
||||||
required this.isClickable,
|
required this.isClickable,
|
||||||
|
required this.showFeaturedReply,
|
||||||
required this.item,
|
required this.item,
|
||||||
required this.onUpdate,
|
required this.onUpdate,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
@ -74,6 +77,7 @@ class PostListEntryWidget extends StatelessWidget {
|
|||||||
item: item,
|
item: item,
|
||||||
isShowEmbed: isShowEmbed,
|
isShowEmbed: isShowEmbed,
|
||||||
isClickable: isNestedClickable,
|
isClickable: isNestedClickable,
|
||||||
|
showFeaturedReply: showFeaturedReply,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
).paddingSymmetric(vertical: 8),
|
).paddingSymmetric(vertical: 8),
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
|
@ -23,6 +23,7 @@ class PostSingleDisplay extends StatelessWidget {
|
|||||||
isClickable: true,
|
isClickable: true,
|
||||||
isShowEmbed: true,
|
isShowEmbed: true,
|
||||||
isNestedClickable: true,
|
isNestedClickable: true,
|
||||||
|
showFeaturedReply: true,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
),
|
),
|
||||||
|
@ -36,6 +36,7 @@ class PostWarpedListWidget extends StatelessWidget {
|
|||||||
isShowEmbed: isShowEmbed,
|
isShowEmbed: isShowEmbed,
|
||||||
isNestedClickable: isNestedClickable,
|
isNestedClickable: isNestedClickable,
|
||||||
isClickable: isClickable,
|
isClickable: isClickable,
|
||||||
|
showFeaturedReply: true,
|
||||||
item: item,
|
item: item,
|
||||||
onUpdate: onUpdate ?? () {},
|
onUpdate: onUpdate ?? () {},
|
||||||
);
|
);
|
||||||
|
@ -1,212 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:solian/exts.dart';
|
|
||||||
import 'package:solian/models/stickers.dart';
|
|
||||||
import 'package:solian/providers/auth.dart';
|
|
||||||
import 'package:solian/widgets/attachments/attachment_editor.dart';
|
|
||||||
|
|
||||||
class StickerUploadDialog extends StatefulWidget {
|
|
||||||
final Sticker? edit;
|
|
||||||
|
|
||||||
const StickerUploadDialog({super.key, this.edit});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StickerUploadDialog> createState() => _StickerUploadDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StickerUploadDialogState extends State<StickerUploadDialog> {
|
|
||||||
final TextEditingController _attachmentController = TextEditingController();
|
|
||||||
final TextEditingController _packController = TextEditingController();
|
|
||||||
final TextEditingController _aliasController = TextEditingController();
|
|
||||||
final TextEditingController _nameController = TextEditingController();
|
|
||||||
|
|
||||||
Color get _unFocusColor =>
|
|
||||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
|
||||||
|
|
||||||
bool _isBusy = false;
|
|
||||||
|
|
||||||
void _promptUploadNewAttachment() {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AttachmentEditorPopup(
|
|
||||||
pool: 'sticker',
|
|
||||||
singleMode: true,
|
|
||||||
imageOnly: true,
|
|
||||||
autoUpload: true,
|
|
||||||
imageMaxHeight: 28,
|
|
||||||
imageMaxWidth: 28,
|
|
||||||
onAdd: (value) {
|
|
||||||
setState(() {
|
|
||||||
_attachmentController.text = value.toString();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
initialAttachments: const [],
|
|
||||||
onRemove: (_) {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _applySticker() async {
|
|
||||||
final AuthProvider auth = Get.find();
|
|
||||||
if (auth.isAuthorized.isFalse) return;
|
|
||||||
|
|
||||||
if ([
|
|
||||||
_nameController.text.isEmpty,
|
|
||||||
_aliasController.text.isEmpty,
|
|
||||||
_packController.text.isEmpty,
|
|
||||||
_attachmentController.text.isEmpty,
|
|
||||||
].any((x) => x)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
Response resp;
|
|
||||||
final client = await auth.configureClient('files');
|
|
||||||
if (widget.edit == null) {
|
|
||||||
resp = await client.post('/stickers', {
|
|
||||||
'name': _nameController.text,
|
|
||||||
'alias': _aliasController.text,
|
|
||||||
'pack_id': int.tryParse(_packController.text),
|
|
||||||
'attachment_id': int.tryParse(_attachmentController.text),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resp = await client.put('/stickers/${widget.edit!.id}', {
|
|
||||||
'name': _nameController.text,
|
|
||||||
'alias': _aliasController.text,
|
|
||||||
'pack_id': int.tryParse(_packController.text),
|
|
||||||
'attachment_id': int.tryParse(_attachmentController.text),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() => _isBusy = false);
|
|
||||||
|
|
||||||
if (resp.statusCode != 200) {
|
|
||||||
context.showErrorDialog(resp.bodyString);
|
|
||||||
} else {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
if (widget.edit != null) {
|
|
||||||
_attachmentController.text = widget.edit!.attachmentId.toString();
|
|
||||||
_packController.text = widget.edit!.packId.toString();
|
|
||||||
_aliasController.text = widget.edit!.alias;
|
|
||||||
_nameController.text = widget.edit!.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_attachmentController.dispose();
|
|
||||||
_packController.dispose();
|
|
||||||
_aliasController.dispose();
|
|
||||||
_nameController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text('stickerUploader'.tr),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
title: Text('stickerUploaderAttachmentNew'.tr),
|
|
||||||
contentPadding: const EdgeInsets.only(left: 16, right: 13),
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
_promptUploadNewAttachment();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
TextField(
|
|
||||||
controller: _attachmentController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
prefixText: '#',
|
|
||||||
labelText: 'stickerUploaderAttachment'.tr,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
TextField(
|
|
||||||
controller: _packController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
prefixText: '#',
|
|
||||||
labelText: 'stickerUploaderPack'.tr,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 6),
|
|
||||||
child: Text(
|
|
||||||
'stickerUploaderPackHint'.tr,
|
|
||||||
style: TextStyle(color: _unFocusColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: _aliasController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: 'stickerUploaderAlias'.tr,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 6),
|
|
||||||
child: Text(
|
|
||||||
'stickerUploaderAliasHint'.tr,
|
|
||||||
style: TextStyle(color: _unFocusColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: _nameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: 'stickerUploaderName'.tr,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 4),
|
|
||||||
child: Text(
|
|
||||||
'stickerUploaderNameHint'.tr,
|
|
||||||
style: TextStyle(color: _unFocusColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor:
|
|
||||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
|
||||||
),
|
|
||||||
onPressed: _isBusy ? null : () => Navigator.pop(context),
|
|
||||||
child: Text('cancel'.tr),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: _isBusy ? null : () => _applySticker(),
|
|
||||||
child: Text('apply'.tr),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -60,9 +60,9 @@ PODS:
|
|||||||
- FirebaseCoreInternal (~> 11.0)
|
- FirebaseCoreInternal (~> 11.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/Logger (~> 8.0)
|
- GoogleUtilities/Logger (~> 8.0)
|
||||||
- FirebaseCoreExtension (11.1.0):
|
- FirebaseCoreExtension (11.2.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- FirebaseCoreInternal (11.1.0):
|
- FirebaseCoreInternal (11.2.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- FirebaseCrashlytics (11.0.0):
|
- FirebaseCrashlytics (11.0.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
@ -73,7 +73,7 @@ PODS:
|
|||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseInstallations (11.1.0):
|
- FirebaseInstallations (11.2.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
@ -87,8 +87,8 @@ PODS:
|
|||||||
- GoogleUtilities/Reachability (~> 8.0)
|
- GoogleUtilities/Reachability (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseRemoteConfigInterop (11.1.0)
|
- FirebaseRemoteConfigInterop (11.2.0)
|
||||||
- FirebaseSessions (11.1.0):
|
- FirebaseSessions (11.2.0):
|
||||||
- FirebaseCore (~> 11.0)
|
- FirebaseCore (~> 11.0)
|
||||||
- FirebaseCoreExtension (~> 11.0)
|
- FirebaseCoreExtension (~> 11.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
@ -344,13 +344,13 @@ SPEC CHECKSUMS:
|
|||||||
firebase_messaging: ce70e6615f0cd906d80b7a651b960d76dad6de56
|
firebase_messaging: ce70e6615f0cd906d80b7a651b960d76dad6de56
|
||||||
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
|
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
|
||||||
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
|
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
|
||||||
FirebaseCoreExtension: aa5c9779c2d0d39d83f1ceb3fdbafe80c4feecfa
|
FirebaseCoreExtension: cda74ddfb001224bd8fd1d6e74698b4ed07803de
|
||||||
FirebaseCoreInternal: adefedc9a88dbe393c4884640a73ec9e8e790f8c
|
FirebaseCoreInternal: 0c569513412da9f3b31bd0b340013bbee8f295c5
|
||||||
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b
|
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b
|
||||||
FirebaseInstallations: d0a8fea5a6fa91abc661591cf57c0f0d70863e57
|
FirebaseInstallations: 771177d89d6c451dc6e50085ec82e2fc77ed0a4a
|
||||||
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742
|
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742
|
||||||
FirebaseRemoteConfigInterop: abf8b1bbc0bf1b84abd22b66746926410bf91a87
|
FirebaseRemoteConfigInterop: 477b26fdeb8fb5fbaf22fa9db5343b42289dc7db
|
||||||
FirebaseSessions: 78f137e68dc01ca71606169ba4ac73b98c13752a
|
FirebaseSessions: adcec8b72d0066a385e3affcd1bcb1ebb3908ce6
|
||||||
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
|
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
|
||||||
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
|
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
|
||||||
flutter_webrtc: 2b4e4a2de70a1485836e40fd71a7a94c77d49bd9
|
flutter_webrtc: 2b4e4a2de70a1485836e40fd71a7a94c77d49bd9
|
||||||
|
@ -701,6 +701,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
@ -843,6 +844,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
@ -870,6 +872,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
|
12
pubspec.lock
12
pubspec.lock
@ -909,14 +909,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.7.0"
|
version: "10.7.0"
|
||||||
freezed_annotation:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: freezed_annotation
|
|
||||||
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.4"
|
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1894,10 +1886,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e"
|
sha256: "4058172e418eb7e7f2058dcb7657d451a8fc264afa0dea4dbd0f304a57131611"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4+2"
|
version: "2.5.4+3"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
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.1+38
|
version: 1.2.1+40
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.4 <4.0.0"
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
@ -68,7 +68,6 @@ dependencies:
|
|||||||
flutter_svg: ^2.0.10+1
|
flutter_svg: ^2.0.10+1
|
||||||
cross_file: ^0.3.4+2
|
cross_file: ^0.3.4+2
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
freezed_annotation: ^2.4.4
|
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
gap: ^3.0.1
|
gap: ^3.0.1
|
||||||
fl_chart: ^0.69.0
|
fl_chart: ^0.69.0
|
||||||
|
Reference in New Issue
Block a user