Compare commits

..

18 Commits

Author SHA1 Message Date
5ebefae961 🚀 Launch 3.3.0+145 2025-11-05 22:48:34 +08:00
d4758674bb 🐛 Trying to fix file chunk issue 2025-11-05 13:13:21 +08:00
f5f1ddc0ea Steam connection 2025-11-04 23:53:17 +08:00
2720b59485 🐛 Fix protocol handling 2025-11-04 23:25:37 +08:00
29b1ac7fce 🐛 Fix tray icon didn't change color on macOS 26 automatically 2025-11-04 23:22:35 +08:00
83ca5551ad ♻️ Refactored the app protocol 2025-11-04 23:08:21 +08:00
611cb024a9 🔨 Update windows version code 2025-11-03 00:20:24 +08:00
74fb56891d 🐛 Fix web build 2025-11-03 00:12:02 +08:00
ac4fa5eb85 🚀 Launch 3.3.0+144 2025-11-02 23:57:31 +08:00
8857718709 🐛 Fix compose toolbar safe area issue 2025-11-02 23:56:48 +08:00
dd17b2b9c1 Scroll gradiant to think as well 2025-11-02 23:55:00 +08:00
848439f664 Chat room scroll gradiant 2025-11-02 23:52:03 +08:00
f83117424d 🐛 Fix tag subscribe used wrong icon 2025-11-02 23:44:11 +08:00
8c19c32c76 Publisher profile collapsible pinned post 2025-11-02 23:36:42 +08:00
d62b2bed80 💄 Optimize publisher page filter select date 2025-11-02 23:34:08 +08:00
5a23eb1768 Stronger filter 2025-11-02 23:30:16 +08:00
5f6e4763d3 🐛 Fix app notification 2025-11-02 23:12:11 +08:00
580c36fb89 🐛 Fix mis placed safe area 2025-11-02 22:45:28 +08:00
33 changed files with 1343 additions and 601 deletions

View File

@@ -43,6 +43,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- App protocol -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
<data android:scheme="solian" />
</intent-filter>
<!-- Deeplinking -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

View File

@@ -163,6 +163,7 @@
"accountConnectionProviderDiscord": "Discord",
"accountConnectionProviderAfdian": "Afdian",
"accountConnectionProviderSpotify": "Spotify",
"accountConnectionProviderSteam": "Steam",
"checkIn": "Check In",
"checkInNone": "Not checked-in yet",
"checkInNoneHint": "Get your fortune tips and daily rewards by checking in.",
@@ -1310,5 +1311,14 @@
"presenceTypeMusic": "Listening to Music",
"presenceTypeWorkout": "Working out",
"articleCompose": "Compose Article",
"backToHub": "Back to Hub"
"backToHub": "Back to Hub",
"advancedFilters": "Advanced Filters",
"searchPosts": "Search Posts",
"sortBy": "Sort by",
"fromDate": "From Date",
"toDate": "To Date",
"popularity": "Popularity",
"descendingOrder": "Descending Order",
"selectDate": "Select Date",
"pinnedPosts": "Pinned Posts"
}

BIN
assets/icons/icon-tray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1 @@
<svg width="2471" height="2500" viewBox="0 0 256 259" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M127.779 0C60.42 0 5.24 52.412 0 119.014l68.724 28.674a35.812 35.812 0 0 1 20.426-6.366c.682 0 1.356.019 2.02.056l30.566-44.71v-.626c0-26.903 21.69-48.796 48.353-48.796 26.662 0 48.352 21.893 48.352 48.796 0 26.902-21.69 48.804-48.352 48.804-.37 0-.73-.009-1.098-.018l-43.593 31.377c.028.582.046 1.163.046 1.735 0 20.204-16.283 36.636-36.294 36.636-17.566 0-32.263-12.658-35.584-29.412L4.41 164.654c15.223 54.313 64.673 94.132 123.369 94.132 70.818 0 128.221-57.938 128.221-129.393C256 57.93 198.597 0 127.779 0zM80.352 196.332l-15.749-6.568c2.787 5.867 7.621 10.775 14.033 13.47 13.857 5.83 29.836-.803 35.612-14.799a27.555 27.555 0 0 0 .046-21.035c-2.768-6.79-7.999-12.086-14.706-14.909-6.67-2.795-13.811-2.694-20.085-.304l16.275 6.79c10.222 4.3 15.056 16.145 10.794 26.46-4.253 10.314-15.998 15.195-26.22 10.895zm121.957-100.29c0-17.925-14.457-32.52-32.217-32.52-17.769 0-32.226 14.595-32.226 32.52 0 17.926 14.457 32.512 32.226 32.512 17.76 0 32.217-14.586 32.217-32.512zm-56.37-.055c0-13.488 10.84-24.42 24.2-24.42 13.368 0 24.208 10.932 24.208 24.42 0 13.488-10.84 24.421-24.209 24.421-13.359 0-24.2-10.933-24.2-24.42z" fill="#1A1918"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,108 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>BUNDLE_ID</key>
<string>dev.solsynth.solian</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Solian</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLSchemes</key>
<array>
<string>solian</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>CLIENT_ID</key>
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCalendarsUsageDescription</key>
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
<key>NSCameraUsageDescription</key>
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
<key>NSFaceIDUsageDescription</key>
<string>Allow the Solar Network verify your ownership of the logged in account and continue your action quickly.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
<key>NSUserActivityTypes</key>
<array>
<string>INStartCallIntent</string>
<string>INSendMessageIntent</string>
</array>
<key>PLIST_VERSION</key>
<string>1</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>audio</string>
<string>remote-notification</string>
<string>voip</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
<dict>
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>BUNDLE_ID</key>
<string>dev.solsynth.solian</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Solian</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>solian</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>CLIENT_ID</key>
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>LSRequiresIPhoneOS</key>
<true />
<key>NSCalendarsUsageDescription</key>
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
<key>NSCameraUsageDescription</key>
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
<key>NSFaceIDUsageDescription</key>
<string>Allow the Solar Network verify your ownership of the logged in account and continue
your action quickly.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
<key>NSUserActivityTypes</key>
<array>
<string>INStartCallIntent</string>
<string>INSendMessageIntent</string>
</array>
<key>PLIST_VERSION</key>
<string>1</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true />
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>audio</string>
<string>remote-notification</string>
<string>voip</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false />
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
</plist>

View File

@@ -30,6 +30,7 @@ import 'package:talker_flutter/talker_flutter.dart';
import 'package:talker_riverpod_logger/talker_riverpod_logger.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart';
import 'package:protocol_handler/protocol_handler.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
@@ -50,6 +51,12 @@ void main() async {
GoRouter.optionURLReflectsImperativeAPIs = true;
}
if (!kIsWeb && (Platform.isLinux || Platform.isMacOS || Platform.isWindows)) {
talker.info("[SplashScreen] Initializing desktop window manager...");
await protocolHandler.register('myprotocol');
talker.info("[SplashScreen] Desktop window manager is ready!");
}
try {
await EasyLocalization.ensureInitialized();

View File

@@ -120,9 +120,11 @@ class ActivityRpcServer {
};
// Set up IPC close handler
_ipcServer!.onSocketClose = (socket) {
handlers['close']?.call(socket);
};
if (!kIsWeb) {
(_ipcServer as dynamic).onSocketClose = (socket) {
handlers['close']?.call(socket);
};
}
await _ipcServer!.start();
} catch (e) {

View File

@@ -30,6 +30,7 @@ Widget getProviderIcon(String provider, {double size = 24, Color? color}) {
case 'github':
case 'discord':
case 'afdian':
case 'steam':
return SvgPicture.asset(
'assets/images/oidc/$providerLower.svg',
width: size,
@@ -64,6 +65,8 @@ String getLocalizedProviderName(String provider) {
return 'accountConnectionProviderAfdian'.tr();
case 'spotify':
return 'accountConnectionProviderSpotify'.tr();
case 'steam':
return 'accountConnectionProviderSteam'.tr();
default:
return provider;
}
@@ -164,6 +167,7 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
'discord',
'afdian',
'spotify',
'steam',
];
Future<void> addConnection() async {
@@ -199,12 +203,7 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
} finally {
if (context.mounted) hideLoadingModal(context);
}
case 'microsoft':
case 'google':
case 'github':
case 'discord':
case 'afdian':
case 'spotify':
default:
final serverUrl = ref.watch(serverUrlProvider);
final accessToken = ref.watch(tokenProvider);
launchUrlString(
@@ -212,9 +211,6 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
);
if (context.mounted) Navigator.pop(context, true);
break;
default:
showSnackBar('accountConnectionAddError'.tr());
return;
}
}

View File

@@ -1,4 +1,5 @@
import "dart:async";
import "dart:math" as math;
import "package:easy_localization/easy_localization.dart";
import "package:file_picker/file_picker.dart";
import "package:flutter/material.dart";
@@ -140,6 +141,9 @@ class ChatRoomScreen extends HookConsumerWidget {
final messageController = useTextEditingController();
final scrollController = useScrollController();
// Scroll animation notifiers
final bottomGradientNotifier = useState(ValueNotifier<double>(0.0));
final messageReplyingTo = useState<SnChatMessage?>(null);
final messageForwardingTo = useState<SnChatMessage?>(null);
final messageEditingTo = useState<SnChatMessage?>(null);
@@ -164,6 +168,12 @@ class ChatRoomScreen extends HookConsumerWidget {
isLoading = true;
messagesNotifier.loadMore().then((_) => isLoading = false);
}
// Update gradient animations
final pixels = scrollController.position.pixels;
// Bottom gradient: appears when not at bottom (pixels > 0)
bottomGradientNotifier.value.value = (pixels / 500.0).clamp(0.0, 1.0);
}
scrollController.addListener(onScroll);
@@ -589,7 +599,9 @@ class ChatRoomScreen extends HookConsumerWidget {
listController: listController,
padding: EdgeInsets.only(
top: 16,
bottom: MediaQuery.of(context).padding.bottom + 16,
bottom:
MediaQuery.of(context).padding.bottom +
80, // Leave space for chat input
),
controller: scrollController,
reverse: true, // Show newest messages at the bottom
@@ -828,7 +840,7 @@ class ChatRoomScreen extends HookConsumerWidget {
),
body: Stack(
children: [
// Messages and Input in Column
// Messages only in Column
Positioned.fill(
child: Column(
children: [
@@ -872,73 +884,6 @@ class ChatRoomScreen extends HookConsumerWidget {
),
),
),
if (!isSelectionMode.value)
chatRoom.when(
data:
(room) => Column(
mainAxisSize: MainAxisSize.min,
children: [
ChatInput(
messageController: messageController,
chatRoom: room!,
onSend: sendMessage,
onClear: () {
if (messageEditingTo.value != null) {
attachments.value.clear();
messageController.clear();
}
messageEditingTo.value = null;
messageReplyingTo.value = null;
messageForwardingTo.value = null;
},
messageEditingTo: messageEditingTo.value,
messageReplyingTo: messageReplyingTo.value,
messageForwardingTo: messageForwardingTo.value,
onPickFile: (bool isPhoto) {
if (isPhoto) {
pickPhotoMedia();
} else {
pickVideoMedia();
}
},
onPickAudio: pickAudioMedia,
onPickGeneralFile: pickGeneralFile,
onLinkAttachment: linkAttachment,
attachments: attachments.value,
onUploadAttachment: uploadAttachment,
onDeleteAttachment: (index) async {
final attachment = attachments.value[index];
if (attachment.isOnCloud &&
!attachment.isLink) {
final client = ref.watch(apiClientProvider);
await client.delete(
'/drive/files/${attachment.data.id}',
);
}
final clone = List.of(attachments.value);
clone.removeAt(index);
attachments.value = clone;
},
onMoveAttachment: (idx, delta) {
if (idx + delta < 0 ||
idx + delta >= attachments.value.length) {
return;
}
final clone = List.of(attachments.value);
clone.insert(idx + delta, clone.removeAt(idx));
attachments.value = clone;
},
onAttachmentsChanged: (newAttachments) {
attachments.value = newAttachments;
},
attachmentProgress: attachmentProgress.value,
),
Gap(MediaQuery.of(context).padding.bottom),
],
),
error: (_, _) => const SizedBox.shrink(),
loading: () => const SizedBox.shrink(),
),
],
),
),
@@ -977,6 +922,112 @@ class ChatRoomScreen extends HookConsumerWidget {
),
),
),
// Bottom gradient - appears when scrolling towards newer messages (behind chat input)
if (!isSelectionMode.value)
AnimatedBuilder(
animation: bottomGradientNotifier.value,
builder:
(context, child) => Positioned(
left: 0,
right: 0,
bottom: 0,
child: Opacity(
opacity: bottomGradientNotifier.value.value,
child: Container(
height: math.min(
MediaQuery.of(context).size.height * 0.1,
128,
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.8),
Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.0),
],
),
),
),
),
),
),
// Chat Input positioned above gradient (higher z-index)
if (!isSelectionMode.value)
Positioned(
left: 0,
right: 0,
bottom: 0, // At the very bottom, above gradient
child: chatRoom.when(
data:
(room) => Column(
mainAxisSize: MainAxisSize.min,
children: [
ChatInput(
messageController: messageController,
chatRoom: room!,
onSend: sendMessage,
onClear: () {
if (messageEditingTo.value != null) {
attachments.value.clear();
messageController.clear();
}
messageEditingTo.value = null;
messageReplyingTo.value = null;
messageForwardingTo.value = null;
},
messageEditingTo: messageEditingTo.value,
messageReplyingTo: messageReplyingTo.value,
messageForwardingTo: messageForwardingTo.value,
onPickFile: (bool isPhoto) {
if (isPhoto) {
pickPhotoMedia();
} else {
pickVideoMedia();
}
},
onPickAudio: pickAudioMedia,
onPickGeneralFile: pickGeneralFile,
onLinkAttachment: linkAttachment,
attachments: attachments.value,
onUploadAttachment: uploadAttachment,
onDeleteAttachment: (index) async {
final attachment = attachments.value[index];
if (attachment.isOnCloud && !attachment.isLink) {
final client = ref.watch(apiClientProvider);
await client.delete(
'/drive/files/${attachment.data.id}',
);
}
final clone = List.of(attachments.value);
clone.removeAt(index);
attachments.value = clone;
},
onMoveAttachment: (idx, delta) {
if (idx + delta < 0 ||
idx + delta >= attachments.value.length) {
return;
}
final clone = List.of(attachments.value);
clone.insert(idx + delta, clone.removeAt(idx));
attachments.value = clone;
},
onAttachmentsChanged: (newAttachments) {
attachments.value = newAttachments;
},
attachmentProgress: attachmentProgress.value,
),
Gap(MediaQuery.of(context).padding.bottom),
],
),
error: (_, _) => const SizedBox.shrink(),
loading: () => const SizedBox.shrink(),
),
),
// Selection mode toolbar
if (isSelectionMode.value)
Positioned(

View File

@@ -201,7 +201,7 @@ class PostCategoryDetailScreen extends HookConsumerWidget {
);
},
icon: const Icon(
Symbols.add_circle,
Symbols.remove_circle,
),
label: Text('unsubscribe'.tr()),
)
@@ -214,7 +214,7 @@ class PostCategoryDetailScreen extends HookConsumerWidget {
);
},
icon: const Icon(
Symbols.remove_circle,
Symbols.add_circle,
),
label: Text('subscribe'.tr()),
),

View File

@@ -326,21 +326,263 @@ class _PublisherHeatmapWidget extends StatelessWidget {
class _PublisherCategoryTabWidget extends StatelessWidget {
final TabController categoryTabController;
final ValueNotifier<bool?> includeReplies;
final ValueNotifier<bool> mediaOnly;
final ValueNotifier<String?> queryTerm;
final ValueNotifier<String?> order;
final ValueNotifier<bool> orderDesc;
final ValueNotifier<int?> periodStart;
final ValueNotifier<int?> periodEnd;
final ValueNotifier<bool> showAdvancedFilters;
const _PublisherCategoryTabWidget({required this.categoryTabController});
const _PublisherCategoryTabWidget({
required this.categoryTabController,
required this.includeReplies,
required this.mediaOnly,
required this.queryTerm,
required this.order,
required this.orderDesc,
required this.periodStart,
required this.periodEnd,
required this.showAdvancedFilters,
});
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TabBar(
controller: categoryTabController,
dividerColor: Colors.transparent,
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
tabs: [
Tab(text: 'all'.tr()),
Tab(text: 'postTypePost'.tr()),
Tab(text: 'postArticle'.tr()),
child: Column(
children: [
TabBar(
controller: categoryTabController,
dividerColor: Colors.transparent,
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
tabs: [
Tab(text: 'all'.tr()),
Tab(text: 'postTypePost'.tr()),
Tab(text: 'postArticle'.tr()),
],
),
const Divider(height: 1),
Column(
children: [
Row(
children: [
Expanded(
child: CheckboxListTile(
title: Text('reply'.tr()),
value: includeReplies.value,
tristate: true,
onChanged: (value) {
// Cycle through: null -> false -> true -> null
if (includeReplies.value == null) {
includeReplies.value = false;
} else if (includeReplies.value == false) {
includeReplies.value = true;
} else {
includeReplies.value = null;
}
},
dense: true,
controlAffinity: ListTileControlAffinity.leading,
secondary: const Icon(Symbols.reply),
),
),
Expanded(
child: CheckboxListTile(
title: Text('attachments'.tr()),
value: mediaOnly.value,
onChanged: (value) {
if (value != null) {
mediaOnly.value = value;
}
},
dense: true,
controlAffinity: ListTileControlAffinity.leading,
secondary: const Icon(Symbols.attachment),
),
),
],
),
CheckboxListTile(
title: Text('descendingOrder'.tr()),
value: orderDesc.value,
onChanged: (value) {
if (value != null) {
orderDesc.value = value;
}
},
dense: true,
controlAffinity: ListTileControlAffinity.leading,
secondary: const Icon(Symbols.sort),
),
],
),
const Divider(height: 1),
ListTile(
title: Text('advancedFilters'.tr()),
leading: const Icon(Symbols.filter_list),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(const Radius.circular(8)),
),
trailing: Icon(
showAdvancedFilters.value
? Symbols.expand_less
: Symbols.expand_more,
),
onTap: () {
showAdvancedFilters.value = !showAdvancedFilters.value;
},
),
if (showAdvancedFilters.value) ...[
const Divider(height: 1),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
decoration: InputDecoration(
labelText: 'search'.tr(),
hintText: 'searchPosts'.tr(),
prefixIcon: const Icon(Symbols.search),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
onChanged: (value) {
queryTerm.value = value.isEmpty ? null : value;
},
),
const Gap(12),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'sortBy'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
value: order.value,
items: [
DropdownMenuItem(value: 'date', child: Text('date'.tr())),
DropdownMenuItem(
value: 'popularity',
child: Text('popularity'.tr()),
),
],
onChanged: (value) {
order.value = value;
},
),
const Gap(12),
Row(
children: [
Expanded(
child: InkWell(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate:
periodStart.value != null
? DateTime.fromMillisecondsSinceEpoch(
periodStart.value! * 1000,
)
: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime.now().add(
const Duration(days: 365),
),
);
if (pickedDate != null) {
periodStart.value =
pickedDate.millisecondsSinceEpoch ~/ 1000;
}
},
child: InputDecorator(
decoration: InputDecoration(
labelText: 'fromDate'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
suffixIcon: const Icon(Symbols.calendar_today),
),
child: Text(
periodStart.value != null
? DateTime.fromMillisecondsSinceEpoch(
periodStart.value! * 1000,
).toString().split(' ')[0]
: 'selectDate'.tr(),
),
),
),
),
const Gap(8),
Expanded(
child: InkWell(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate:
periodEnd.value != null
? DateTime.fromMillisecondsSinceEpoch(
periodEnd.value! * 1000,
)
: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime.now().add(
const Duration(days: 365),
),
);
if (pickedDate != null) {
periodEnd.value =
pickedDate.millisecondsSinceEpoch ~/ 1000;
}
},
child: InputDecorator(
decoration: InputDecoration(
labelText: 'toDate'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
suffixIcon: const Icon(Symbols.calendar_today),
),
child: Text(
periodEnd.value != null
? DateTime.fromMillisecondsSinceEpoch(
periodEnd.value! * 1000,
).toString().split(' ')[0]
: 'selectDate'.tr(),
),
),
),
),
],
),
],
),
),
],
],
),
);
@@ -423,7 +665,18 @@ class PublisherProfileScreen extends HookConsumerWidget {
categoryTab.value = categoryTabController.index;
});
final includeReplies = useState<bool?>(null);
final mediaOnly = useState(false);
final queryTerm = useState<String?>(null);
final order = useState<String?>('date'); // 'popularity' or 'date'
final orderDesc = useState(
true,
); // true for descending, false for ascending
final periodStart = useState<int?>(null);
final periodEnd = useState<int?>(null);
final showAdvancedFilters = useState(false);
final subscribing = useState(false);
final isPinnedExpanded = useState(true);
Future<void> subscribe() async {
final apiClient = ref.watch(apiClientProvider);
@@ -494,21 +747,66 @@ class PublisherProfileScreen extends HookConsumerWidget {
child: CustomScrollView(
slivers: [
SliverGap(16),
SliverPostList(pubName: name, pinned: true),
SliverToBoxAdapter(
child: Card(
margin: EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
child: ListTile(
title: Text('pinnedPosts'.tr()),
leading: const Icon(Symbols.push_pin),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
),
trailing: Icon(
isPinnedExpanded.value
? Symbols.expand_less
: Symbols.expand_more,
),
onTap:
() =>
isPinnedExpanded.value =
!isPinnedExpanded.value,
),
),
),
...[
if (isPinnedExpanded.value)
SliverPostList(pubName: name, pinned: true),
],
SliverToBoxAdapter(
child: _PublisherCategoryTabWidget(
categoryTabController: categoryTabController,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
orderDesc: orderDesc,
periodStart: periodStart,
periodEnd: periodEnd,
showAdvancedFilters: showAdvancedFilters,
),
),
SliverPostList(
key: ValueKey(categoryTab.value),
key: ValueKey(
'${categoryTab.value}-${includeReplies.value}-${mediaOnly.value}-${queryTerm.value}-${order.value}-${orderDesc.value}-${periodStart.value}-${periodEnd.value}',
),
pubName: name,
pinned: false,
type: switch (categoryTab.value) {
1 => 0,
2 => 1,
_ => null,
},
type:
categoryTab.value == 1
? 0
: (categoryTab.value == 2 ? 1 : null),
includeReplies: includeReplies.value,
mediaOnly: mediaOnly.value,
queryTerm: queryTerm.value,
order: order.value,
orderDesc: orderDesc.value,
periodStart: periodStart.value,
periodEnd: periodEnd.value,
),
SliverGap(
MediaQuery.of(context).padding.bottom + 16,
@@ -617,21 +915,60 @@ class PublisherProfileScreen extends HookConsumerWidget {
heatmap: heatmap,
).padding(vertical: 4),
),
SliverPostList(pubName: name, pinned: true),
SliverToBoxAdapter(
child: Card(
margin: EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
child: ListTile(
title: Text('pinnedPosts'.tr()),
trailing: Icon(
isPinnedExpanded.value
? Symbols.expand_less
: Symbols.expand_more,
),
onTap:
() =>
isPinnedExpanded.value =
!isPinnedExpanded.value,
),
),
),
...[
if (isPinnedExpanded.value)
SliverPostList(pubName: name, pinned: true),
],
SliverToBoxAdapter(
child: _PublisherCategoryTabWidget(
categoryTabController: categoryTabController,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
orderDesc: orderDesc,
periodStart: periodStart,
periodEnd: periodEnd,
showAdvancedFilters: showAdvancedFilters,
),
),
SliverPostList(
key: ValueKey(categoryTab.value),
key: ValueKey(
'${categoryTab.value}-${includeReplies.value}-${mediaOnly.value}-${queryTerm.value}-${order.value}-${orderDesc.value}-${periodStart.value}-${periodEnd.value}',
),
pubName: name,
pinned: false,
type: switch (categoryTab.value) {
1 => 0,
2 => 1,
_ => null,
},
type:
categoryTab.value == 1
? 0
: (categoryTab.value == 2 ? 1 : null),
includeReplies: includeReplies.value,
mediaOnly: mediaOnly.value,
queryTerm: queryTerm.value,
order: order.value,
orderDesc: orderDesc.value,
periodStart: periodStart.value,
periodEnd: periodEnd.value,
),
SliverGap(MediaQuery.of(context).padding.bottom + 16),
],

View File

@@ -1,4 +1,5 @@
import "dart:convert";
import "dart:math" as math;
import "package:dio/dio.dart";
import "package:easy_localization/easy_localization.dart";
import "package:flutter/material.dart";
@@ -57,6 +58,9 @@ class ThoughtScreen extends HookConsumerWidget {
final listController = useMemoized(() => ListController(), []);
// Scroll animation notifiers
final bottomGradientNotifier = useState(ValueNotifier<double>(0.0));
// Update local thoughts when provider data changes
useEffect(() {
thoughts.whenData((data) {
@@ -86,6 +90,20 @@ class ThoughtScreen extends HookConsumerWidget {
return null;
}, [localThoughts.value.length, isStreaming.value]);
// Add scroll listener for gradient animations
useEffect(() {
void onScroll() {
// Update gradient animations
final pixels = scrollController.position.pixels;
// Bottom gradient: appears when not at bottom (pixels > 0)
bottomGradientNotifier.value.value = (pixels / 500.0).clamp(0.0, 1.0);
}
scrollController.addListener(onScroll);
return () => scrollController.removeListener(onScroll);
}, [scrollController]);
void sendMessage() async {
if (messageController.text.trim().isEmpty) return;
@@ -258,65 +276,120 @@ class ThoughtScreen extends HookConsumerWidget {
const Gap(8),
],
),
body: Center(
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: Column(
children: [
Expanded(
child: thoughts.when(
data:
(thoughtList) => SuperListView.builder(
listController: listController,
controller: scrollController,
padding: const EdgeInsets.only(top: 16, bottom: 16),
reverse: true,
itemCount:
localThoughts.value.length +
(isStreaming.value ? 1 : 0),
itemBuilder: (context, index) {
if (isStreaming.value && index == 0) {
return ThoughtItem(
isStreaming: true,
streamingText: streamingText.value,
reasoningChunks: reasoningChunks.value,
streamingFunctionCalls: functionCalls.value,
);
}
final thoughtIndex =
isStreaming.value ? index - 1 : index;
final thought = localThoughts.value[thoughtIndex];
return ThoughtItem(
thought: thought,
thoughtIndex: thoughtIndex,
);
},
body: Stack(
children: [
// Thoughts list
Center(
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: Column(
children: [
Expanded(
child: thoughts.when(
data:
(thoughtList) => SuperListView.builder(
listController: listController,
controller: scrollController,
padding: EdgeInsets.only(
top: 16,
bottom:
MediaQuery.of(context).padding.bottom +
80, // Leave space for thought input
),
reverse: true,
itemCount:
localThoughts.value.length +
(isStreaming.value ? 1 : 0),
itemBuilder: (context, index) {
if (isStreaming.value && index == 0) {
return ThoughtItem(
isStreaming: true,
streamingText: streamingText.value,
reasoningChunks: reasoningChunks.value,
streamingFunctionCalls: functionCalls.value,
);
}
final thoughtIndex =
isStreaming.value ? index - 1 : index;
final thought = localThoughts.value[thoughtIndex];
return ThoughtItem(
thought: thought,
thoughtIndex: thoughtIndex,
);
},
),
loading:
() =>
const Center(child: CircularProgressIndicator()),
error:
(error, _) => ResponseErrorWidget(
error: error,
onRetry:
() =>
selectedSequenceId.value != null
? ref.invalidate(
thoughtSequenceProvider(
selectedSequenceId.value!,
),
)
: null,
),
),
),
],
),
),
),
// Bottom gradient - appears when scrolling towards newer thoughts (behind thought input)
AnimatedBuilder(
animation: bottomGradientNotifier.value,
builder:
(context, child) => Positioned(
left: 0,
right: 0,
bottom: 0,
child: Opacity(
opacity: bottomGradientNotifier.value.value,
child: Container(
height: math.min(
MediaQuery.of(context).size.height * 0.1,
128,
),
loading:
() => const Center(child: CircularProgressIndicator()),
error:
(error, _) => ResponseErrorWidget(
error: error,
onRetry:
() =>
selectedSequenceId.value != null
? ref.invalidate(
thoughtSequenceProvider(
selectedSequenceId.value!,
),
)
: null,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.8),
Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.0),
],
),
),
),
),
),
),
// Thought Input positioned above gradient (higher z-index)
Positioned(
left: 0,
right: 0,
bottom: 0, // At the very bottom, above gradient
child: Center(
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: ThoughtInput(
messageController: messageController,
isStreaming: isStreaming.value,
onSend: sendMessage,
),
),
ThoughtInput(
messageController: messageController,
isStreaming: isStreaming.value,
onSend: sendMessage,
),
],
),
),
),
],
),
);
}

View File

@@ -1,4 +1,5 @@
import "dart:convert";
import "dart:math" as math;
import "package:dio/dio.dart";
import "package:easy_localization/easy_localization.dart";
import "package:flutter/material.dart";
@@ -54,6 +55,9 @@ class ThoughtSheet extends HookConsumerWidget {
final listController = useMemoized(() => ListController(), []);
// Scroll animation notifiers
final bottomGradientNotifier = useState(ValueNotifier<double>(0.0));
// Scroll to bottom when thoughts change or streaming state changes
useEffect(() {
if (localThoughts.value.isNotEmpty || isStreaming.value) {
@@ -68,6 +72,20 @@ class ThoughtSheet extends HookConsumerWidget {
return null;
}, [localThoughts.value.length, isStreaming.value]);
// Add scroll listener for gradient animations
useEffect(() {
void onScroll() {
// Update gradient animations
final pixels = scrollController.position.pixels;
// Bottom gradient: appears when not at bottom (pixels > 0)
bottomGradientNotifier.value.value = (pixels / 500.0).clamp(0.0, 1.0);
}
scrollController.addListener(onScroll);
return () => scrollController.removeListener(onScroll);
}, [scrollController]);
void sendMessage() async {
if (messageController.text.trim().isEmpty) return;
@@ -196,47 +214,103 @@ class ThoughtSheet extends HookConsumerWidget {
return SheetScaffold(
titleText: currentTopic.value ?? 'aiThought'.tr(),
child: Center(
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: Column(
children: [
Expanded(
child: SuperListView.builder(
listController: listController,
controller: scrollController,
padding: const EdgeInsets.only(top: 16, bottom: 16),
reverse: true,
itemCount:
localThoughts.value.length + (isStreaming.value ? 1 : 0),
itemBuilder: (context, index) {
if (isStreaming.value && index == 0) {
return ThoughtItem(
isStreaming: true,
streamingText: streamingText.value,
reasoningChunks: reasoningChunks.value,
streamingFunctionCalls: functionCalls.value,
);
}
final thoughtIndex = isStreaming.value ? index - 1 : index;
final thought = localThoughts.value[thoughtIndex];
return ThoughtItem(
thought: thought,
thoughtIndex: thoughtIndex,
);
},
child: Stack(
children: [
// Thoughts list
Center(
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: Column(
children: [
Expanded(
child: SuperListView.builder(
listController: listController,
controller: scrollController,
padding: EdgeInsets.only(
top: 16,
bottom:
MediaQuery.of(context).padding.bottom +
80, // Leave space for thought input
),
reverse: true,
itemCount:
localThoughts.value.length +
(isStreaming.value ? 1 : 0),
itemBuilder: (context, index) {
if (isStreaming.value && index == 0) {
return ThoughtItem(
isStreaming: true,
streamingText: streamingText.value,
reasoningChunks: reasoningChunks.value,
streamingFunctionCalls: functionCalls.value,
);
}
final thoughtIndex =
isStreaming.value ? index - 1 : index;
final thought = localThoughts.value[thoughtIndex];
return ThoughtItem(
thought: thought,
thoughtIndex: thoughtIndex,
);
},
),
),
],
),
),
),
// Bottom gradient - appears when scrolling towards newer thoughts (behind thought input)
AnimatedBuilder(
animation: bottomGradientNotifier.value,
builder:
(context, child) => Positioned(
left: 0,
right: 0,
bottom: 0,
child: Opacity(
opacity: bottomGradientNotifier.value.value,
child: Container(
height: math.min(
MediaQuery.of(context).size.height * 0.1,
128,
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.8),
Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.0),
],
),
),
),
),
),
),
// Thought Input positioned above gradient (higher z-index)
Positioned(
left: 0,
right: 0,
bottom: 0, // At the very bottom, above gradient
child: Center(
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: ThoughtInput(
messageController: messageController,
isStreaming: isStreaming.value,
onSend: sendMessage,
attachedMessages: attachedMessages,
attachedPosts: attachedPosts,
),
),
ThoughtInput(
messageController: messageController,
isStreaming: isStreaming.value,
onSend: sendMessage,
attachedMessages: attachedMessages,
attachedPosts: attachedPosts,
),
],
),
),
),
],
),
);
}

View File

@@ -22,7 +22,8 @@ class TrayService {
await trayManager.setIcon(
Platform.isWindows
? 'assets/icons/icon.ico'
: 'assets/icons/icon-outline.svg',
: 'assets/icons/icon-tray.png',
isTemplate: Platform.isMacOS,
);
final menu = Menu(

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:cross_file/cross_file.dart';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
@@ -16,16 +15,15 @@ class FileUploader {
FileUploader(this._client);
/// Calculates the MD5 hash of a file.
Future<String> _calculateFileHash(XFile file) async {
final bytes = await file.readAsBytes();
/// Calculates the MD5 hash of file bytes.
String _calculateFileHash(Uint8List bytes) {
final digest = md5.convert(bytes);
return digest.toString();
}
/// Creates an upload task for the given file.
Future<Map<String, dynamic>> createUploadTask({
required XFile file,
required Uint8List bytes,
required String fileName,
required String contentType,
String? poolId,
@@ -34,8 +32,8 @@ class FileUploader {
String? expiredAt,
int? chunkSize,
}) async {
final hash = await _calculateFileHash(file);
final fileSize = await file.length();
final hash = _calculateFileHash(bytes);
final fileSize = bytes.length;
final response = await _client.post(
'/drive/files/upload/create',
@@ -83,7 +81,7 @@ class FileUploader {
/// Uploads a file in chunks using the multi-part API.
Future<SnCloudFile> uploadFile({
required XFile file,
required Uint8List bytes,
required String fileName,
required String contentType,
String? poolId,
@@ -94,7 +92,7 @@ class FileUploader {
}) async {
// Step 1: Create upload task
final createResponse = await createUploadTask(
file: file,
bytes: bytes,
fileName: fileName,
contentType: contentType,
poolId: poolId,
@@ -114,24 +112,10 @@ class FileUploader {
final chunksCount = createResponse['chunks_count'] as int;
// Step 2: Upload chunks
final stream = file.openRead();
final chunks = <Uint8List>[];
int bytesRead = 0;
final buffer = BytesBuilder();
await for (final chunk in stream) {
buffer.add(chunk);
bytesRead += chunk.length;
if (bytesRead >= chunkSize) {
chunks.add(buffer.takeBytes());
bytesRead = 0;
}
}
// Add remaining bytes as last chunk
if (buffer.length > 0) {
chunks.add(buffer.takeBytes());
for (int i = 0; i < bytes.length; i += chunkSize) {
final end = i + chunkSize > bytes.length ? bytes.length : i + chunkSize;
chunks.add(Uint8List.fromList(bytes.sublist(i, end)));
}
// Ensure we have the correct number of chunks
@@ -225,20 +209,34 @@ class FileUploader {
Completer<SnCloudFile?> completer,
) {
String actualMimetype = getMimeType(fileData);
late XFile file;
String actualFilename = fileData.displayName ?? 'randomly_file';
Uint8List? byteData;
Uint8List? bytes;
// Handle the data based on what's in the UniversalFile
final data = fileData.data;
if (data is XFile) {
file = data;
actualFilename = fileData.displayName ?? data.name;
// Read bytes from XFile
data
.readAsBytes()
.then((readBytes) {
_performUpload(
bytes: readBytes,
fileName: fileData.displayName ?? data.name,
contentType: actualMimetype,
client: client,
poolId: poolId,
onProgress: onProgress,
completer: completer,
);
})
.catchError((e) {
completer.completeError(e);
});
return completer;
} else if (data is List<int> || data is Uint8List) {
byteData = data is List<int> ? Uint8List.fromList(data) : data;
bytes = data is List<int> ? Uint8List.fromList(data) : data;
actualFilename = fileData.displayName ?? 'uploaded_file';
file = XFile.fromData(byteData!, mimeType: actualMimetype);
} else if (data is SnCloudFile) {
// If the file is already on the cloud, just return it
completer.complete(data);
@@ -252,15 +250,40 @@ class FileUploader {
return completer;
}
if (bytes != null) {
_performUpload(
bytes: bytes,
fileName: actualFilename,
contentType: actualMimetype,
client: client,
poolId: poolId,
onProgress: onProgress,
completer: completer,
);
}
return completer;
}
// Helper method to perform the actual upload
static void _performUpload({
required Uint8List bytes,
required String fileName,
required String contentType,
required Dio client,
String? poolId,
Function(double progress, Duration estimate)? onProgress,
required Completer<SnCloudFile?> completer,
}) {
final uploader = FileUploader(client);
// Call progress start
onProgress?.call(0.0, Duration.zero);
uploader
.uploadFile(
file: file,
fileName: actualFilename,
contentType: actualMimetype,
bytes: bytes,
fileName: fileName,
contentType: contentType,
poolId: poolId,
)
.then((result) {
@@ -272,8 +295,6 @@ class FileUploader {
completer.completeError(e);
throw e;
});
return completer;
}
/// Gets the MIME type of a UniversalFile.

View File

@@ -17,8 +17,7 @@ class NotificationCard extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final icon = Symbols.info;
return InkWell(
borderRadius: BorderRadius.all(Radius.circular(8)),
return GestureDetector(
onTap: () {
if (notification.meta['action_uri'] != null) {
var uri = notification.meta['action_uri'] as String;

View File

@@ -1,8 +1,9 @@
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:island/pods/activity/activity_rpc.dart';
import 'package:island/pods/websocket.dart';
import 'package:island/route.dart';
@@ -15,57 +16,61 @@ import 'package:island/widgets/tour/tour.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
class AppWrapper extends HookConsumerWidget with TrayListener {
class AppWrapper extends ConsumerStatefulWidget {
final Widget child;
const AppWrapper({super.key, required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
useEffect(() {
StreamSubscription? ntySubs;
StreamSubscription? appLinksSubs;
Future(() async {
final appLinks = AppLinks();
ConsumerState<AppWrapper> createState() => _AppWrapperState();
}
if (context.mounted) ntySubs = setupNotificationListener(context, ref);
class _AppWrapperState extends ConsumerState<AppWrapper>
with ProtocolListener, TrayListener {
StreamSubscription? ntySubs;
bool networkStateShowing = false;
final sharingService = SharingIntentService();
if (context.mounted) sharingService.initialize(context);
if (context.mounted) UpdateService().checkForUpdates(context);
@override
void initState() {
super.initState();
protocolHandler.addListener(this);
Future(() async {
if (mounted) ntySubs = setupNotificationListener(context, ref);
TrayService.instance.initialize(this);
final sharingService = SharingIntentService();
if (mounted) sharingService.initialize(context);
if (mounted) UpdateService().checkForUpdates(context);
ref.read(rpcServerStateProvider.notifier).start();
TrayService.instance.initialize(this);
final initialUri = await appLinks.getLatestLink();
if (initialUri != null && context.mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_handleDeepLink(initialUri, ref);
});
}
ref.read(rpcServerStateProvider.notifier).start();
appLinksSubs = appLinks.uriLinkStream.listen((uri) {
_handleDeepLink(uri, ref);
final initialUrl = await protocolHandler.getInitialUrl();
if (initialUrl != null && mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_handleDeepLink(Uri.parse(initialUrl), ref);
});
});
}
});
}
return () {
ref.read(rpcServerProvider).stop();
TrayService.instance.dispose(this);
ntySubs?.cancel();
appLinksSubs?.cancel();
};
}, const []);
@override
void dispose() {
protocolHandler.removeListener(this);
ref.read(rpcServerProvider).stop();
TrayService.instance.dispose(this);
ntySubs?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final wsNotifier = ref.watch(websocketStateProvider.notifier);
final websocketState = ref.watch(websocketStateProvider);
final networkStateShowing = useState(false);
if (websocketState == WebSocketState.duplicateDevice()) {
if (!networkStateShowing.value) {
if (!networkStateShowing) {
WidgetsBinding.instance.addPostFrameCallback((_) {
networkStateShowing.value = true;
setState(() => networkStateShowing = true);
showModalBottomSheet(
context: context,
isScrollControlled: true,
@@ -73,12 +78,17 @@ class AppWrapper extends HookConsumerWidget with TrayListener {
builder:
(context) =>
NetworkStatusSheet(onReconnect: () => wsNotifier.connect()),
).then((_) => networkStateShowing.value = false);
).then((_) => setState(() => networkStateShowing = false));
});
}
}
return TourTriggerWidget(key: UniqueKey(), child: child);
return TourTriggerWidget(key: UniqueKey(), child: widget.child);
}
@override
void onProtocolUrlReceived(String url) {
_handleDeepLink(Uri.parse(url), ref);
}
void _trayIconPrimaryAction() {
@@ -106,13 +116,17 @@ class AppWrapper extends HookConsumerWidget with TrayListener {
void _handleDeepLink(Uri uri, WidgetRef ref) {
final router = ref.read(routerProvider);
String path = '/${uri.path}';
String path = '/${uri.host}${uri.path}';
if (uri.queryParameters.isNotEmpty) {
path =
Uri.parse(
path,
).replace(queryParameters: uri.queryParameters).toString();
}
router.go(path);
router.push(path);
if (!kIsWeb &&
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
windowManager.show();
}
}
}

View File

@@ -423,6 +423,7 @@ class PostComposeCard extends HookConsumerWidget {
state: composeState,
originalPost: originalPost,
isCompact: true,
useSafeArea: isContained,
),
),
),

View File

@@ -14,12 +14,14 @@ class ComposeToolbar extends HookConsumerWidget {
final ComposeState state;
final SnPost? originalPost;
final bool isCompact;
final bool useSafeArea;
const ComposeToolbar({
super.key,
required this.state,
this.originalPost,
this.isCompact = false,
this.useSafeArea = false,
});
@override
@@ -200,7 +202,12 @@ class ComposeToolbar extends HookConsumerWidget {
),
),
],
).padding(horizontal: 8, vertical: 4),
).padding(
horizontal: 8,
top: 4,
bottom:
useSafeArea ? MediaQuery.of(context).padding.bottom + 4 : 4,
),
),
),
);

View File

@@ -24,6 +24,12 @@ class PostListNotifier extends _$PostListNotifier
bool? pinned,
bool shuffle = false,
bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) {
return fetch(cursor: null);
}
@@ -36,14 +42,20 @@ class PostListNotifier extends _$PostListNotifier
final queryParams = {
'offset': offset,
'take': _pageSize,
'replies': includeReplies,
'orderDesc': orderDesc,
if (shuffle) 'shuffle': shuffle,
if (pubName != null) 'pub': pubName,
if (realm != null) 'realm': realm,
if (type != null) 'type': type,
if (tags != null) 'tags': tags,
if (categories != null) 'categories': categories,
if (shuffle) 'shuffle': true,
if (pinned != null) 'pinned': pinned,
if (includeReplies != null) 'includeReplies': includeReplies,
if (order != null) 'order': order,
if (periodStart != null) 'periodStart': periodStart,
if (periodEnd != null) 'periodEnd': periodEnd,
if (queryTerm != null) 'query': queryTerm,
if (mediaOnly != null) 'media': mediaOnly,
};
final response = await client.get(
@@ -82,6 +94,14 @@ class SliverPostList extends HookConsumerWidget {
final List<String>? tags;
final bool shuffle;
final bool? pinned;
final bool? includeReplies;
final bool? mediaOnly;
final String? queryTerm;
// Can be "populaurity", other value will be treated as "date"
final String? order;
final int? periodStart;
final int? periodEnd;
final bool? orderDesc;
final PostItemType itemType;
final Color? backgroundColor;
final EdgeInsets? padding;
@@ -99,6 +119,13 @@ class SliverPostList extends HookConsumerWidget {
this.tags,
this.shuffle = false,
this.pinned,
this.includeReplies,
this.mediaOnly,
this.queryTerm,
this.order,
this.orderDesc = true,
this.periodStart,
this.periodEnd,
this.itemType = PostItemType.regular,
this.backgroundColor,
this.padding,
@@ -118,6 +145,13 @@ class SliverPostList extends HookConsumerWidget {
tags: tags,
shuffle: shuffle,
pinned: pinned,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc ?? true,
);
return PagingHelperSliverView(
provider: provider,

View File

@@ -6,7 +6,7 @@ part of 'post_list.dart';
// RiverpodGenerator
// **************************************************************************
String _$postListNotifierHash() => r'fc139ad4df0deb67bcbb949560319f2f7fbfb503';
String _$postListNotifierHash() => r'8241120dc3c2004387c6cf881e5cb9224cbd3a97';
/// Copied from Dart SDK
class _SystemHash {
@@ -39,6 +39,12 @@ abstract class _$PostListNotifier
late final bool? pinned;
late final bool shuffle;
late final bool? includeReplies;
late final bool? mediaOnly;
late final String? queryTerm;
late final String? order;
late final int? periodStart;
late final int? periodEnd;
late final bool orderDesc;
FutureOr<CursorPagingData<SnPost>> build({
String? pubName,
@@ -49,6 +55,12 @@ abstract class _$PostListNotifier
bool? pinned,
bool shuffle = false,
bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
});
}
@@ -72,6 +84,12 @@ class PostListNotifierFamily
bool? pinned,
bool shuffle = false,
bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) {
return PostListNotifierProvider(
pubName: pubName,
@@ -82,6 +100,12 @@ class PostListNotifierFamily
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
);
}
@@ -98,6 +122,12 @@ class PostListNotifierFamily
pinned: provider.pinned,
shuffle: provider.shuffle,
includeReplies: provider.includeReplies,
mediaOnly: provider.mediaOnly,
queryTerm: provider.queryTerm,
order: provider.order,
periodStart: provider.periodStart,
periodEnd: provider.periodEnd,
orderDesc: provider.orderDesc,
);
}
@@ -133,6 +163,12 @@ class PostListNotifierProvider
bool? pinned,
bool shuffle = false,
bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) : this._internal(
() =>
PostListNotifier()
@@ -143,7 +179,13 @@ class PostListNotifierProvider
..tags = tags
..pinned = pinned
..shuffle = shuffle
..includeReplies = includeReplies,
..includeReplies = includeReplies
..mediaOnly = mediaOnly
..queryTerm = queryTerm
..order = order
..periodStart = periodStart
..periodEnd = periodEnd
..orderDesc = orderDesc,
from: postListNotifierProvider,
name: r'postListNotifierProvider',
debugGetCreateSourceHash:
@@ -161,6 +203,12 @@ class PostListNotifierProvider
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
);
PostListNotifierProvider._internal(
@@ -178,6 +226,12 @@ class PostListNotifierProvider
required this.pinned,
required this.shuffle,
required this.includeReplies,
required this.mediaOnly,
required this.queryTerm,
required this.order,
required this.periodStart,
required this.periodEnd,
required this.orderDesc,
}) : super.internal();
final String? pubName;
@@ -188,6 +242,12 @@ class PostListNotifierProvider
final bool? pinned;
final bool shuffle;
final bool? includeReplies;
final bool? mediaOnly;
final String? queryTerm;
final String? order;
final int? periodStart;
final int? periodEnd;
final bool orderDesc;
@override
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
@@ -202,6 +262,12 @@ class PostListNotifierProvider
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
);
}
@@ -219,7 +285,13 @@ class PostListNotifierProvider
..tags = tags
..pinned = pinned
..shuffle = shuffle
..includeReplies = includeReplies,
..includeReplies = includeReplies
..mediaOnly = mediaOnly
..queryTerm = queryTerm
..order = order
..periodStart = periodStart
..periodEnd = periodEnd
..orderDesc = orderDesc,
from: from,
name: null,
dependencies: null,
@@ -233,6 +305,12 @@ class PostListNotifierProvider
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
),
);
}
@@ -256,7 +334,13 @@ class PostListNotifierProvider
other.tags == tags &&
other.pinned == pinned &&
other.shuffle == shuffle &&
other.includeReplies == includeReplies;
other.includeReplies == includeReplies &&
other.mediaOnly == mediaOnly &&
other.queryTerm == queryTerm &&
other.order == order &&
other.periodStart == periodStart &&
other.periodEnd == periodEnd &&
other.orderDesc == orderDesc;
}
@override
@@ -270,6 +354,12 @@ class PostListNotifierProvider
hash = _SystemHash.combine(hash, pinned.hashCode);
hash = _SystemHash.combine(hash, shuffle.hashCode);
hash = _SystemHash.combine(hash, includeReplies.hashCode);
hash = _SystemHash.combine(hash, mediaOnly.hashCode);
hash = _SystemHash.combine(hash, queryTerm.hashCode);
hash = _SystemHash.combine(hash, order.hashCode);
hash = _SystemHash.combine(hash, periodStart.hashCode);
hash = _SystemHash.combine(hash, periodEnd.hashCode);
hash = _SystemHash.combine(hash, orderDesc.hashCode);
return _SystemHash.finish(hash);
}
@@ -302,6 +392,24 @@ mixin PostListNotifierRef
/// The parameter `includeReplies` of this provider.
bool? get includeReplies;
/// The parameter `mediaOnly` of this provider.
bool? get mediaOnly;
/// The parameter `queryTerm` of this provider.
String? get queryTerm;
/// The parameter `order` of this provider.
String? get order;
/// The parameter `periodStart` of this provider.
int? get periodStart;
/// The parameter `periodEnd` of this provider.
int? get periodEnd;
/// The parameter `orderDesc` of this provider.
bool get orderDesc;
}
class _PostListNotifierProviderElement
@@ -331,6 +439,18 @@ class _PostListNotifierProviderElement
@override
bool? get includeReplies =>
(origin as PostListNotifierProvider).includeReplies;
@override
bool? get mediaOnly => (origin as PostListNotifierProvider).mediaOnly;
@override
String? get queryTerm => (origin as PostListNotifierProvider).queryTerm;
@override
String? get order => (origin as PostListNotifierProvider).order;
@override
int? get periodStart => (origin as PostListNotifierProvider).periodStart;
@override
int? get periodEnd => (origin as PostListNotifierProvider).periodEnd;
@override
bool get orderDesc => (origin as PostListNotifierProvider).orderDesc;
}
// ignore_for_file: type=lint

View File

@@ -542,6 +542,7 @@ class ReactionDetailsPopup extends HookConsumerWidget {
notifierRefreshable: provider.notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero,
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {

View File

@@ -13,7 +13,6 @@
#include <flutter_timezone/flutter_timezone_plugin.h>
#include <flutter_udid/flutter_udid_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <gtk/gtk_plugin.h>
#include <irondash_engine_context/irondash_engine_context_plugin.h>
#include <livekit_client/live_kit_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
@@ -51,9 +50,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);

View File

@@ -10,7 +10,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_timezone
flutter_udid
flutter_webrtc
gtk
irondash_engine_context
livekit_client
media_kit_libs_linux

View File

@@ -5,7 +5,6 @@
import FlutterMacOS
import Foundation
import app_links
import connectivity_plus
import device_info_plus
import file_picker
@@ -31,6 +30,7 @@ import media_kit_video
import package_info_plus
import pasteboard
import path_provider_foundation
import protocol_handler_macos
import record_macos
import screen_retriever_macos
import share_plus
@@ -47,7 +47,6 @@ import wakelock_plus
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
@@ -73,6 +72,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ProtocolHandlerMacosPlugin.register(with: registry.registrar(forPlugin: "ProtocolHandlerMacosPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))

View File

@@ -1,6 +1,4 @@
PODS:
- app_links (6.4.1):
- FlutterMacOS
- connectivity_plus (0.0.1):
- FlutterMacOS
- croppy (0.0.1):
@@ -199,6 +197,8 @@ PODS:
- PromisesObjC (2.4.0)
- PromisesSwift (2.4.0):
- PromisesObjC (= 2.4.0)
- protocol_handler_macos (0.0.1):
- FlutterMacOS
- record_macos (1.1.0):
- FlutterMacOS
- SAMKeychain (1.5.3)
@@ -256,7 +256,6 @@ PODS:
- FlutterMacOS
DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
@@ -284,6 +283,7 @@ DEPENDENCIES:
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- protocol_handler_macos (from `Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos`)
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
@@ -323,8 +323,6 @@ SPEC REPOS:
- WebRTC-SDK
EXTERNAL SOURCES:
app_links:
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
connectivity_plus:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
croppy:
@@ -379,6 +377,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
protocol_handler_macos:
:path: Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos
record_macos:
:path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos
screen_retriever_macos:
@@ -409,7 +409,6 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f
connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e
croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
@@ -454,6 +453,7 @@ SPEC CHECKSUMS:
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_macos: f9cd7b13bcaf6b0425f7410cbe52376cb843a936
record_macos: 43194b6c06ca6f8fa132e2acea72b202b92a0f5b
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f

View File

@@ -1,45 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.social-networking</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key>
<false/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict/>
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.social-networking</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key>
<false />
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true />
<key>UISceneConfigurations</key>
<dict />
</dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>solian</string>
</array>
</dict>
</array>
</dict>
</dict>
</plist>

View File

@@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: f871a7d1b686bea1f13722aa51ab31554d05c81f47054d6de48cc8c45153508b
sha256: "8a1f5f3020ef2a74fb93f7ab3ef127a8feea33a7a2276279113660784ee7516a"
url: "https://pub.dev"
source: hosted
version: "1.3.63"
version: "1.3.64"
analyzer:
dependency: transitive
description:
@@ -49,38 +49,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
app_links:
dependency: "direct main"
description:
name: app_links
sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
archive:
dependency: "direct main"
description:
@@ -341,10 +309,10 @@ packages:
dependency: "direct main"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.6"
version: "3.0.7"
csslib:
dependency: transitive
description:
@@ -429,10 +397,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da"
url: "https://pub.dev"
source: hosted
version: "11.5.0"
version: "11.3.0"
device_info_plus_platform_interface:
dependency: transitive
description:
@@ -637,34 +605,34 @@ packages:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "3cfc4089e61e810ffb531af63cfde2c8cfd36f12dc14fdba359e623992311015"
sha256: bfb80d92eee10a6585ebd5a7e60de5caf0f2c06329e5676c0578130aea1bfe85
url: "https://pub.dev"
source: hosted
version: "12.0.3"
version: "12.0.4"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: "775fc18d9b00a014362510a33f76f1f34deb30f69a64edcb41a7dfd0ebd9cf98"
sha256: "3b803077907def997044774f6c022d8e9204e9c0f5e205e3572d887c93dafd72"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
version: "5.0.4"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "6eafa8fef5fdca6c922ac3e353c9a093c12344a3ba996e65fd40f8db0a00d26f"
sha256: "0dbd96dbe77b51185319000c0078477fdcffb4abb0018c362dd9afb9845c1e06"
url: "https://pub.dev"
source: hosted
version: "0.6.0+3"
version: "0.6.1"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "132e1c311bc41e7d387b575df0aacdf24efbf4930365eb61042be5bde3978f03"
sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "4.2.1"
firebase_core_platform_interface:
dependency: transitive
description:
@@ -677,50 +645,50 @@ packages:
dependency: transitive
description:
name: firebase_core_web
sha256: ecde2def458292404a4fcd3731ee4992fd631a0ec359d2d67c33baa8da5ec8ae
sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "3.3.0"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: "2f53d0d3c0875105b166f09bdf026026bb74f26930c6ffcd5d65b311ca5a9f58"
sha256: c3ebe3ed9f3b1d36c0864a4a28b041fcc2686f11fb2a4f7891319ea8d1d161cc
url: "https://pub.dev"
source: hosted
version: "5.0.3"
version: "5.0.4"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: de5c857525fc9576cd3fc30fc72422bc2371179ecae110246c0135ae896c6de3
sha256: a8ca502fe3aa48b4f0b9e6e3bc0019085a247b5d1214cd342a189457975662db
url: "https://pub.dev"
source: hosted
version: "3.8.14"
version: "3.8.15"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "5021279acd1cb5ccaceaa388e616e82cc4a2e4d862f02637df0e8ab766e6900a"
sha256: "22086f857d2340f5d973776cfd542d3fb30cf98e1c643c3aa4a7520bb12745bb"
url: "https://pub.dev"
source: hosted
version: "16.0.3"
version: "16.0.4"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: f3a16c51f02055ace2a7c16ccb341c1f1b36b67c13270a48bcef68c1d970bbe8
sha256: a59920cbf2eb7c83d34a5f354331210ffec116b216dc72d864d8b8eb983ca398
url: "https://pub.dev"
source: hosted
version: "4.7.3"
version: "4.7.4"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "3eb9a1382caeb95b370f21e36d4a460496af777c9c2ef5df9b90d4803982c069"
sha256: "1183e40e6fd2a279a628951cc3b639fcf5ffe7589902632db645011eb70ebefb"
url: "https://pub.dev"
source: hosted
version: "4.0.3"
version: "4.1.0"
fixnum:
dependency: transitive
description:
@@ -778,10 +746,10 @@ packages:
dependency: "direct main"
description:
name: flutter_card_swiper
sha256: "9fbe75c913c0a01f34f9f98068ad198e396695fcf8abfa433cc53652fceb5617"
sha256: "895c6974729b51cf73a35f1b58ab57a0af3293131319e2cbccac3bc57ffcd69f"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
version: "7.2.0"
flutter_colorpicker:
dependency: "direct main"
description:
@@ -1103,10 +1071,10 @@ packages:
dependency: "direct main"
description:
name: flutter_svg
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.2.2"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -1201,10 +1169,10 @@ packages:
dependency: transitive
description:
name: get_it
sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b
sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9
url: "https://pub.dev"
source: hosted
version: "8.2.0"
version: "8.3.0"
glob:
dependency: transitive
description:
@@ -1245,14 +1213,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.3.4"
gtk:
dependency: transitive
description:
name: gtk
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
url: "https://pub.dev"
source: hosted
version: "2.1.0"
highlight:
dependency: transitive
description:
@@ -1957,6 +1917,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.0"
protocol_handler:
dependency: "direct main"
description:
name: protocol_handler
sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9
url: "https://pub.dev"
source: hosted
version: "0.2.0"
protocol_handler_android:
dependency: transitive
description:
name: protocol_handler_android
sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
protocol_handler_ios:
dependency: transitive
description:
name: protocol_handler_ios
sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
protocol_handler_macos:
dependency: transitive
description:
name: protocol_handler_macos
sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
protocol_handler_platform_interface:
dependency: transitive
description:
name: protocol_handler_platform_interface
sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
protocol_handler_windows:
dependency: transitive
description:
name: protocol_handler_windows
sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4
url: "https://pub.dev"
source: hosted
version: "0.2.0"
provider:
dependency: transitive
description:
@@ -2395,14 +2403,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.1"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
sqflite:
dependency: transitive
description:
@@ -2551,18 +2551,18 @@ packages:
dependency: transitive
description:
name: syncfusion_flutter_core
sha256: a24e9ec04e03c2c14b7b41b1afe60e455adef09b244ab4c425ce6c5b8f58c9ce
sha256: "825670efc828e18e14ff310cbcf6de91c8ff73b55c75ae6868a2b3cd87e88b6c"
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
syncfusion_flutter_pdf:
dependency: transitive
description:
name: syncfusion_flutter_pdf
sha256: "8d98edae5c5d3aba2125de49bd37882da124409021d4f3de5730eb93d8247a81"
sha256: "50fc39ba628167949e89374488de67cc788646d6c0dce2a9fd047dbeecb841c2"
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
syncfusion_flutter_pdfviewer:
dependency: "direct main"
description:
@@ -2575,50 +2575,50 @@ packages:
dependency: transitive
description:
name: syncfusion_flutter_signaturepad
sha256: d2f87273133283efd550370403462739329ad0ad1bdae6a73998be1fb30e9ee1
sha256: "6e60af61cec5ee7436b01ecb3fd944602aed42887789a67a27314678ad04d38a"
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
syncfusion_pdfviewer_linux:
dependency: transitive
description:
name: syncfusion_pdfviewer_linux
sha256: "1edc9c3408526ad25c7a0d67b0f12a3e427225fd7e87d67319cd6e19bbfaeb45"
sha256: fea25c996ed8850504c80c8fe7541aa3dce3d5159af0e92519d13e10a9509601
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
syncfusion_pdfviewer_macos:
dependency: transitive
description:
name: syncfusion_pdfviewer_macos
sha256: "962911d8cba4d3f5f0bf5dee5ef87cc0b31651431adfad56a51c47057859fb50"
sha256: "389326ef84ad9d14858d4f5f14da36267faa894134c38080ae30d55d2e3f4ce9"
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
syncfusion_pdfviewer_platform_interface:
dependency: transitive
description:
name: syncfusion_pdfviewer_platform_interface
sha256: a701825a971f1bb8540ad39611872ebc08ed0955a0a9600f263cb6cb85826ce2
sha256: "2c3098dc644965feee66f4bf726ef433a51eecc16ccea71e052ba19897f3c2c5"
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
syncfusion_pdfviewer_web:
dependency: transitive
description:
name: syncfusion_pdfviewer_web
sha256: e3eda11636a013a7ebab01a573b079d3a52c695474ac7c5239f65d5952d8da82
sha256: db5b91493aefb2e9faeb6425ea4f3c5f8eb7907a29ffca2e33564987a9c1c1f4
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
syncfusion_pdfviewer_windows:
dependency: transitive
description:
name: syncfusion_pdfviewer_windows
sha256: "9f8def51da7277bda5796ba27fff357a697689e226be397d7c52e353824cf961"
sha256: d2e4d64e5cd96ea678b1ff66588897ce59e17e2685c1153995af53d91327a143
url: "https://pub.dev"
source: hosted
version: "31.2.4"
version: "31.2.5"
synchronized:
dependency: transitive
description:
@@ -2719,10 +2719,10 @@ packages:
dependency: "direct main"
description:
name: tray_manager
sha256: "537e539f48cd82d8ee2240d4330158c7b44c7e043e8e18b5811f2f8f6b7df25a"
sha256: c5fd83b0ae4d80be6eaedfad87aaefab8787b333b8ebd064b0e442a81006035b
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.5.2"
tuple:
dependency: transitive
description:
@@ -2840,10 +2840,10 @@ packages:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.1"
version: "4.5.2"
vector_graphics:
dependency: transitive
description:
@@ -2984,10 +2984,10 @@ packages:
dependency: transitive
description:
name: win32_registry
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "1.1.5"
window_manager:
dependency: "direct main"
description:

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 3.3.0+143
version: 3.3.0+145
environment:
sdk: ^3.7.2
@@ -50,7 +50,7 @@ dependencies:
flutter_markdown_latex: ^0.3.4
markdown: ^7.3.0
flutter_highlight: ^0.7.0
uuid: ^4.5.1
uuid: ^4.5.2
url_launcher: ^6.3.2
google_fonts: ^6.3.2
gap: ^3.0.1
@@ -67,7 +67,8 @@ dependencies:
flutter_inappwebview: ^6.1.5
animations: ^2.1.0
package_info_plus: ^9.0.0
device_info_plus: ^11.5.0
device_info_plus: ^11.3.0
protocol_handler: ^0.2.0
tus_client_dart:
git: https://github.com/LittleSheep2Code/tus_client.git
cross_file: ^0.3.5
@@ -78,9 +79,9 @@ dependencies:
image_picker_android: ^0.8.13+7
super_context_menu: ^0.9.1
modal_bottom_sheet: ^3.0.0
firebase_messaging: ^16.0.3
firebase_messaging: ^16.0.4
flutter_udid: ^4.0.0
firebase_core: ^4.2.0
firebase_core: ^4.2.1
web_socket_channel: ^3.0.3
material_symbols_icons: ^4.2874.0
drift: ^2.28.2
@@ -93,7 +94,7 @@ dependencies:
relative_time: ^5.0.0
dropdown_button2: ^2.3.9
riverpod_paging_utils: ^0.8.1
crypto: ^3.0.6
crypto: ^3.0.7
avatar_stack: ^3.0.0
markdown_widget: ^2.3.2+8
visibility_detector: ^0.4.0+2
@@ -115,7 +116,7 @@ dependencies:
flutter_timezone: ^5.0.1
fl_chart: ^1.1.1
sign_in_with_apple: ^7.0.1
flutter_svg: ^2.2.1
flutter_svg: ^2.2.2
native_exif: ^0.6.2
local_auth: ^3.0.0
flutter_secure_storage: ^9.2.4
@@ -134,13 +135,13 @@ dependencies:
flutter_app_update: ^3.2.2
archive: ^4.0.7
process_run: ^1.2.4
firebase_crashlytics: ^5.0.3
firebase_analytics: ^12.0.3
firebase_crashlytics: ^5.0.4
firebase_analytics: ^12.0.4
material_color_utilities: ^0.11.1
screenshot: ^3.0.0
flutter_card_swiper: ^7.1.0
flutter_card_swiper: ^7.2.0
file_saver: ^0.3.1
tray_manager: ^0.5.1
tray_manager: ^0.5.2
flutter_webrtc: ^1.2.0
flutter_local_notifications: ^19.5.0
wakelock_plus: ^1.4.0
@@ -158,7 +159,6 @@ dependencies:
talker_logger: ^5.0.2
talker_dio_logger: ^5.0.2
talker_riverpod_logger: ^5.0.1
app_links: ^6.4.1
syncfusion_flutter_pdfviewer: ^31.1.21
swipe_to: ^1.0.6
fl_heatmap: ^0.4.6

View File

@@ -1,6 +1,6 @@
; ==================================================
#define AppVersion "3.2.0"
#define BuildNumber "134"
#define AppVersion "3.3.0"
#define BuildNumber "144"
; ==================================================
#define FullVersion AppVersion + "." + BuildNumber

View File

@@ -6,7 +6,6 @@
#include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <dart_ipc/dart_ipc_plugin_c_api.h>
#include <file_saver/file_saver_plugin.h>
@@ -25,6 +24,7 @@
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h>
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
#include <record_windows/record_windows_plugin_c_api.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
@@ -38,8 +38,6 @@
#include <windows_notification/windows_notification_plugin_c_api.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DartIpcPluginCApiRegisterWithRegistrar(
@@ -76,6 +74,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
RecordWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(

View File

@@ -3,7 +3,6 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
app_links
connectivity_plus
dart_ipc
file_saver
@@ -22,6 +21,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
media_kit_libs_windows_video
media_kit_video
pasteboard
protocol_handler_windows
record_windows
screen_retriever_windows
share_plus

View File

@@ -1,51 +1,23 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "app_links/app_links_plugin_c_api.h"
#include "flutter_window.h"
#include "utils.h"
bool SendAppLinkToInstance(const std::wstring& title) {
// Find our exact window
HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", title.c_str());
if (hwnd) {
// Dispatch new link to current window
SendAppLink(hwnd);
// (Optional) Restore our window to front in same state
WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) };
GetWindowPlacement(hwnd, &place);
switch(place.showCmd) {
case SW_SHOWMAXIMIZED:
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
break;
case SW_SHOWMINIMIZED:
ShowWindow(hwnd, SW_RESTORE);
break;
default:
ShowWindow(hwnd, SW_NORMAL);
break;
}
SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
SetForegroundWindow(hwnd);
// END (Optional) Restore
// Window has been found, don't create another one.
return true;
}
return false;
}
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command)
{
if (SendAppLinkToInstance(L"solian")) {
return EXIT_SUCCESS;
HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", L"Solian");
if (hwnd != NULL)
{
DispatchToProtocolHandler(hwnd);
::ShowWindow(hwnd, SW_NORMAL);
::SetForegroundWindow(hwnd);
return EXIT_FAILURE;
}
// Attach to console when present (e.g., 'flutter run') or create a