diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6ec6d4d..90c7c6f 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -46,12 +46,37 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 84228ef..422f9d2 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -987,7 +987,7 @@
INFOPLIST_FILE = SolianNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1029,7 +1029,7 @@
INFOPLIST_FILE = SolianNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1068,7 +1068,7 @@
INFOPLIST_FILE = SolianNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index eae3471..a8d5ad0 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -11,6 +11,21 @@ import UIKit
) -> Bool {
UNUserNotificationCenter.current().delegate = notifyDelegate
+ let replyableMessageCategory = UNNotificationCategory(
+ identifier: "REPLYABLE_MESSAGE",
+ actions: [
+ UNTextInputNotificationAction(
+ identifier: "reply_action",
+ title: "Reply",
+ options: []
+ ),
+ ],
+ intentIdentifiers: [],
+ options: []
+ )
+
+ UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
+
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
diff --git a/ios/Runner/NotifyDelegate.swift b/ios/Runner/NotifyDelegate.swift
index 21c4a59..f611767 100644
--- a/ios/Runner/NotifyDelegate.swift
+++ b/ios/Runner/NotifyDelegate.swift
@@ -10,40 +10,51 @@ import Alamofire
class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
- if let textResponse = response as? UNTextInputNotificationResponse {
- let content = response.notification.request.content
- guard let metadata = content.userInfo["meta"] as? [AnyHashable: Any] else {
- return
- }
-
- var token: String? = UserDefaults.standard.getFlutterToken()
- if token == nil {
- return
- }
-
- let serverUrl = UserDefaults.standard.getServerUrl()
- let url = "\(serverUrl)/chat/\(metadata["room_id"] ?? "")/messages"
-
- let parameters: [String: Any?] = [
- "content": textResponse.userText,
- "replied_message_id": metadata["message_id"]
- ]
-
- AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders(
- [HTTPHeader(name: "Authorization", value: "AtField \(token!)")]
- ))
- .validate()
- .responseString { response in
- switch response.result {
- case .success(_):
- break
- case .failure(let error):
- print("Failed to send chat reply message: \(error)")
- break
- }
- }
+ guard let textResponse = response as? UNTextInputNotificationResponse else {
+ completionHandler()
+ return
+ }
+
+ let content = response.notification.request.content
+
+ // Only handle replies for new messages
+ guard let notificationType = content.userInfo["type"] as? String, notificationType == "messages.new" else {
+ completionHandler()
+ return
+ }
+
+ guard let metadata = content.userInfo["meta"] as? [AnyHashable: Any] else {
+ completionHandler()
+ return
}
- completionHandler()
+ guard let token = UserDefaults.standard.getFlutterToken() else {
+ completionHandler()
+ return
+ }
+
+ let serverUrl = UserDefaults.standard.getServerUrl()
+ let url = "\(serverUrl)/chat/\(metadata["room_id"] ?? "")/messages"
+
+ let parameters: [String: Any?] = [
+ "content": textResponse.userText,
+ "replied_message_id": metadata["message_id"]
+ ]
+
+ AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders(
+ [HTTPHeader(name: "Authorization", value: "AtField \(token)")]
+ ))
+ .validate()
+ .responseString { response in
+ switch response.result {
+ case .success(_):
+ break
+ case .failure(let error):
+ print("Failed to send chat reply message: \(error)")
+ break
+ }
+ // Call completion handler after network request is finished
+ completionHandler()
+ }
}
}
diff --git a/ios/SolianNotificationService/NotificationService.swift b/ios/SolianNotificationService/NotificationService.swift
index 3a5d18e..47944b0 100644
--- a/ios/SolianNotificationService/NotificationService.swift
+++ b/ios/SolianNotificationService/NotificationService.swift
@@ -60,21 +60,7 @@ class NotificationService: UNNotificationServiceExtension {
let pfpIdentifier = meta["pfp"] as? String
- let replyableMessageCategory = UNNotificationCategory(
- identifier: content.categoryIdentifier,
- actions: [
- UNTextInputNotificationAction(
- identifier: "reply_action",
- title: "Reply",
- options: []
- ),
- ],
- intentIdentifiers: [],
- options: []
- )
-
- UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
- content.categoryIdentifier = replyableMessageCategory.identifier
+ content.categoryIdentifier = "REPLYABLE_MESSAGE"
let metaCopy = meta as? [String: Any] ?? [:]
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
diff --git a/lib/route.dart b/lib/route.dart
index 6abd41d..3144e47 100644
--- a/lib/route.dart
+++ b/lib/route.dart
@@ -53,7 +53,10 @@ final routerProvider = Provider((ref) {
// Standalone routes without bottom navigation
GoRoute(
path: '/posts/compose',
- builder: (context, state) => const PostComposeScreen(),
+ builder:
+ (context, state) => PostComposeScreen(
+ initialState: state.extra as PostComposeInitialState?,
+ ),
),
GoRoute(
path: '/posts/:id/edit',
diff --git a/lib/screens/posts/pub_profile.dart b/lib/screens/posts/pub_profile.dart
index 4a65dc6..9c79b0a 100644
--- a/lib/screens/posts/pub_profile.dart
+++ b/lib/screens/posts/pub_profile.dart
@@ -54,25 +54,26 @@ Future publisherSubscriptionStatus(
@riverpod
Future publisherAppbarForcegroundColor(Ref ref, String pubName) async {
- final publisher = await ref.watch(publisherProvider(pubName).future);
- if (publisher.background == null) return null;
- final palette = await PaletteGenerator.fromImageProvider(
- CloudImageWidget.provider(
- fileId: publisher.background!.id,
- serverUrl: ref.watch(serverUrlProvider),
- ),
- );
- final dominantColor = palette.dominantColor?.color;
- if (dominantColor == null) return null;
- return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
+ try {
+ final publisher = await ref.watch(publisherProvider(pubName).future);
+ if (publisher.background == null) return null;
+ final palette = await PaletteGenerator.fromImageProvider(
+ CloudImageWidget.provider(
+ fileId: publisher.background!.id,
+ serverUrl: ref.watch(serverUrlProvider),
+ ),
+ );
+ final dominantColor = palette.dominantColor?.color;
+ if (dominantColor == null) return null;
+ return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
+ } catch (_) {
+ return null;
+ }
}
class PublisherProfileScreen extends HookConsumerWidget {
final String name;
- const PublisherProfileScreen({
- super.key,
- required this.name,
- });
+ const PublisherProfileScreen({super.key, required this.name});
@override
Widget build(BuildContext context, WidgetRef ref) {
diff --git a/lib/widgets/content/cloud_file_collection.dart b/lib/widgets/content/cloud_file_collection.dart
index f367227..a3b2ff3 100644
--- a/lib/widgets/content/cloud_file_collection.dart
+++ b/lib/widgets/content/cloud_file_collection.dart
@@ -186,13 +186,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
Future saveToGallery() async {
try {
// Show loading indicator
- final scaffold = ScaffoldMessenger.of(context);
- scaffold.showSnackBar(
- const SnackBar(
- content: Text('Saving image to gallery...'),
- duration: Duration(seconds: 1),
- ),
- );
+ showSnackBar('Saving image to gallery...');
// Get the image URL
final client = ref.watch(apiClientProvider);
@@ -209,12 +203,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
await Gal.putImage(filePath, album: 'Solar Network');
// Show success message
- scaffold.showSnackBar(
- const SnackBar(
- content: Text('Image saved to gallery'),
- duration: Duration(seconds: 2),
- ),
- );
+ showSnackBar('Image saved to gallery');
} catch (e) {
showErrorAlert(e);
}
diff --git a/lib/widgets/publisher/publisher_card.dart b/lib/widgets/publisher/publisher_card.dart
index 1132c02..d77f368 100644
--- a/lib/widgets/publisher/publisher_card.dart
+++ b/lib/widgets/publisher/publisher_card.dart
@@ -29,7 +29,7 @@ class PublisherCard extends ConsumerWidget {
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {
- context.push('/publishers/${publisher.id}');
+ context.push('/publishers/${publisher.name}');
},
child: AspectRatio(
aspectRatio: 16 / 7,
diff --git a/lib/widgets/share/share_sheet.dart b/lib/widgets/share/share_sheet.dart
index 49e3ff6..b0ff17d 100644
--- a/lib/widgets/share/share_sheet.dart
+++ b/lib/widgets/share/share_sheet.dart
@@ -13,6 +13,7 @@ import 'package:island/pods/network.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/services/file.dart';
+import 'package:mime/mime.dart';
import 'dart:io';
import 'package:path/path.dart' as path;
@@ -149,9 +150,9 @@ class _ShareSheetState extends ConsumerState {
case ShareContentType.file:
if (widget.content.files != null) {
// Convert XFiles to UniversalFiles
- for (final xFile in widget.content.files!) {
- final file = File(xFile.path);
- final mimeType = xFile.mimeType;
+ for (final file in widget.content.files!) {
+ var mimeType = file.mimeType;
+ mimeType ??= lookupMimeType(file.path);
UniversalFileType fileType;
if (mimeType?.startsWith('image/') == true) {
diff --git a/pubspec.lock b/pubspec.lock
index b7b094c..e4ea605 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1470,7 +1470,7 @@ packages:
source: hosted
version: "1.16.0"
mime:
- dependency: transitive
+ dependency: "direct main"
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
@@ -1785,10 +1785,10 @@ packages:
dependency: transitive
description:
name: record_linux
- sha256: "29e7735b05c1944bb6c9b72a36c08d4a1b24117e712d6a9523c003bde12bf484"
+ sha256: "0626678a092c75ce6af1e32fe7fd1dea709b92d308bc8e3b6d6348e2430beb95"
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.1.1"
record_macos:
dependency: transitive
description:
@@ -2254,10 +2254,10 @@ packages:
dependency: transitive
description:
name: synchronized
- sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
+ sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
url: "https://pub.dev"
source: hosted
- version: "3.3.1"
+ version: "3.4.0"
table_calendar:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 7a08991..33a1b33 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -126,6 +126,7 @@ dependencies:
git:
url: https://github.com/lionelmennig/textfield_tags.git
ref: fixes/allow-controller-re-registration
+ mime: ^2.0.0
dev_dependencies:
flutter_test: