Compare commits
10 Commits
3.2.0+134
...
838d18013b
Author | SHA1 | Date | |
---|---|---|---|
838d18013b
|
|||
3f7902e463
|
|||
54560ad5d8
|
|||
0c729db639
|
|||
1fbaac8d88
|
|||
b9dc724f0b
|
|||
a2cc55696f
|
|||
e79f857feb
|
|||
affba29c04
|
|||
756746b144
|
File diff suppressed because it is too large
Load Diff
1079
assets/i18n/es-ES.json
Normal file
1079
assets/i18n/es-ES.json
Normal file
File diff suppressed because it is too large
Load Diff
1079
assets/i18n/ja-JP.json
Normal file
1079
assets/i18n/ja-JP.json
Normal file
File diff suppressed because it is too large
Load Diff
1079
assets/i18n/ko-KR.json
Normal file
1079
assets/i18n/ko-KR.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1079
assets/i18n/zh-OG.json
Normal file
1079
assets/i18n/zh-OG.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -122,10 +122,6 @@
|
||||
"addVideo": "添加視頻",
|
||||
"addPhoto": "添加照片",
|
||||
"addFile": "添加文件",
|
||||
"uploadFile": "上傳文件",
|
||||
"settingsDefaultPool": "選擇文件池",
|
||||
"settingsDefaultPoolHelper": "爲文件上傳選擇一個默認池",
|
||||
|
||||
"createDirectMessage": "創建新私人消息",
|
||||
"gotoDirectMessage": "前往私信",
|
||||
"react": "反應",
|
||||
@@ -307,8 +303,7 @@
|
||||
"notifications": "通知",
|
||||
"posts": "帖子",
|
||||
"settingsBackgroundImage": "背景圖片",
|
||||
"settingsBackgroundImageEnable": "顯示背景圖片",
|
||||
"settingsBackgroundImageClear": "清除背景圖片",
|
||||
"settingsBackgroundImageClear": "清除背景圖片",
|
||||
"settingsBackgroundGenerateColor": "從背景圖像生成主題色",
|
||||
"messageNone": "沒有內容可顯示",
|
||||
"unreadMessages": {
|
||||
@@ -319,8 +314,6 @@
|
||||
"settingsRealmCompactView": "緊湊領域視圖",
|
||||
"settingsMixedFeed": "混合動態",
|
||||
"settingsAutoTranslate": "自動翻譯",
|
||||
"settingsDataSavingMode": "低數據模式",
|
||||
"dataSavingHint": "低數據模式",
|
||||
"settingsHideBottomNav": "隱藏底部導航",
|
||||
"settingsSoundEffects": "音效",
|
||||
"settingsAprilFoolFeatures": "愚人節功能",
|
||||
@@ -675,7 +668,6 @@
|
||||
"publisherFeatureDevelopDescription": "為你的開發者解鎖包括應用套件,API 及更多開發功能。",
|
||||
"publisherFeatureDevelopHint": "目前該功能還在開發中,你需要邀請才可解鎖。",
|
||||
"learnMore": "瞭解更多",
|
||||
"discoverWebArticles": "來自站外的文章",
|
||||
"webArticlesStand": "文章亭",
|
||||
"about": "關於",
|
||||
"somethingWentWrong": "發生了一些錯誤",
|
||||
@@ -698,8 +690,6 @@
|
||||
"sharePostPhoto": "通過圖片分享帖子",
|
||||
"wouldYouLikeToNavigateToChat": "你想要前往聊天頁面嗎?",
|
||||
"abuseReports": "舉報",
|
||||
"discoverRealms": "發現領域",
|
||||
"discoverPublishers": "發現發佈者",
|
||||
"membershipCancel": "取消會員訂閱",
|
||||
"membershipCancelConfirm": "你確定要取消會員訂閱嗎?",
|
||||
"membershipCancelHint": "你確定要取消會員訂閱嗎?你將不會再次被扣費。你的會員資格將在當前計費週期結束前保持有效。並且你將無法重新訂閱,直到當前訂閱結束。",
|
||||
@@ -763,19 +753,19 @@
|
||||
"markAsSensitive": "標記為敏感",
|
||||
"fileName": "文件名",
|
||||
"sensitiveCategories": {
|
||||
"language": "語言",
|
||||
"sexualContent": "色情內容",
|
||||
"violence": "暴力",
|
||||
"profanity": "褻瀆",
|
||||
"hateSpeech": "仇恨言論",
|
||||
"racism": "種族主義",
|
||||
"adultContent": "成人內容",
|
||||
"drugAbuse": "藥物濫用",
|
||||
"alcoholAbuse": "酗酒",
|
||||
"gambling": "賭博",
|
||||
"selfHarm": "自殘",
|
||||
"childAbuse": "虐待兒童",
|
||||
"other": "其他"
|
||||
"language": "Language",
|
||||
"sexualContent": "Sexual Content",
|
||||
"violence": "Violence",
|
||||
"profanity": "Profanity",
|
||||
"hateSpeech": "Hate Speech",
|
||||
"racism": "Racism",
|
||||
"adultContent": "Adult Content",
|
||||
"drugAbuse": "Drug Abuse",
|
||||
"alcoholAbuse": "Alcohol Abuse",
|
||||
"gambling": "Gambling",
|
||||
"selfHarm": "Self-harm",
|
||||
"childAbuse": "Child Abuse",
|
||||
"other": "Other"
|
||||
},
|
||||
"poll": "投票",
|
||||
"pollsRecent": "最近投票",
|
||||
@@ -819,6 +809,159 @@
|
||||
"one": "+{} 個文件被摺疊",
|
||||
"other": "+{} 個文件被摺疊"
|
||||
},
|
||||
"pollQuestions": "Questions",
|
||||
"pollAnswerSubmitted": "Poll answer has been submitted.",
|
||||
"modifyAnswers": "Modify Answers",
|
||||
"back": "Back",
|
||||
"submit": "Submit",
|
||||
"pollOptionDefaultLabel": "Option 1",
|
||||
"pollUpdated": "Poll updated.",
|
||||
"pollCreated": "Poll created.",
|
||||
"pollCreate": "Create Poll",
|
||||
"pollEdit": "Edit Poll",
|
||||
"pollPreviewJsonDebug": "Debug Preview",
|
||||
"pollTitleRequired": "Title is required",
|
||||
"pollEndDateOptional": "End date & time (optional)",
|
||||
"notSet": "Not set",
|
||||
"pick": "Pick",
|
||||
"clear": "Clear",
|
||||
"questions": "Questions",
|
||||
"pollAddQuestion": "Add question",
|
||||
"pollQuestionTypeSingleChoice": "Single choice",
|
||||
"pollQuestionTypeMultipleChoice": "Multiple choice",
|
||||
"pollQuestionTypeFreeText": "Free text",
|
||||
"pollQuestionTypeYesNo": "Yes / No",
|
||||
"pollQuestionTypeRating": "Rating",
|
||||
"pollNoQuestionsYet": "No questions yet",
|
||||
"pollNoQuestionsHint": "Use \"Add question\" to start building your poll.",
|
||||
"pollDebugPreview": "Debug Preview",
|
||||
"pollUntitledQuestion": "Untitled question",
|
||||
"moveUp": "Move up",
|
||||
"moveDown": "Move down",
|
||||
"required": "Required",
|
||||
"pollQuestionTitle": "Question title",
|
||||
"pollQuestionTitleRequired": "Question title is required",
|
||||
"pollQuestionDescriptionOptional": "Question description (optional)",
|
||||
"options": "Options",
|
||||
"pollAddOption": "Add option",
|
||||
"pollOptionLabel": "Option label",
|
||||
"pollLongTextAnswerPreview": "Long text answer (preview)",
|
||||
"pollShortTextAnswerPreview": "Short text answer (preview)",
|
||||
"award": "Award",
|
||||
"awardPost": "Award Post",
|
||||
"awardMessage": "Message",
|
||||
"awardMessageHint": "Enter your award message...",
|
||||
"awardAttitude": "Attitude",
|
||||
"awardAttitudePositive": "Positive",
|
||||
"awardAttitudeNegative": "Negative",
|
||||
"awardAmount": "Amount",
|
||||
"awardAmountHint": "Enter amount...",
|
||||
"awardAmountRequired": "Amount is required",
|
||||
"awardAmountInvalid": "Please enter a valid amount",
|
||||
"awardMessageTooLong": "Message is too long (max 4096 characters)",
|
||||
"awardSuccess": "Award sent successfully!",
|
||||
"awardSubmit": "Award",
|
||||
"awardPostPreview": "Post Preview",
|
||||
"awardNoContent": "No content available",
|
||||
"awardByPublisher": "By {}",
|
||||
"awardBenefits": "Award Benefits",
|
||||
"awardBenefitsDescription": "Awarding this post increases its value and visibility. Higher valued posts have a better chance of being featured and highlighted in the community.",
|
||||
"checkInResultLevel5": "Happy Birthday 🥳",
|
||||
"region": "Region",
|
||||
"accountRegionHint": "This region will be used for content delivery and localization.",
|
||||
"settingsCustomFontsHelper": "Use comma to seprate.",
|
||||
"settingsBackgroundImageEnable": "顯示背景圖片",
|
||||
"settingsDataSavingMode": "低數據模式",
|
||||
"dataSavingHint": "低數據模式",
|
||||
"postTypePost": "Post",
|
||||
"searchDrafts": "Search drafts...",
|
||||
"noSearchResults": "No search results",
|
||||
"contactMethodMakePublic": "Make Public",
|
||||
"contactMethodMakePrivate": "Make Private",
|
||||
"contactMethodPublic": "Public",
|
||||
"contactMethodPrivate": "Private",
|
||||
"discoverRealms": "發現領域",
|
||||
"discoverPublishers": "發現發佈者",
|
||||
"discoverShuffledPost": "Random Posts",
|
||||
"projects": "Projects",
|
||||
"noProjects": "No projects found.",
|
||||
"deleteProject": "Delete Project",
|
||||
"deleteProjectHint": "Are you sure you want to delete this project? This action cannot be undone.",
|
||||
"createProject": "Create Project",
|
||||
"editProject": "Edit Project",
|
||||
"projectDetails": "Project Details",
|
||||
"createBot": "Create Bot",
|
||||
"bots": "Bots",
|
||||
"noBots": "No bots yet.",
|
||||
"deleteBotHint": "Are you sure you want to delete this bot? This action cannot be undone.",
|
||||
"deleteBot": "Delete Bot",
|
||||
"discoverWebArticles": "來自站外的文章",
|
||||
"messageJumpNotLoaded": "The referenced message was not loaded, unable to jump to it.",
|
||||
"postUnlinkRealm": "No linked realm",
|
||||
"postSlug": "Slug",
|
||||
"postSlugHint": "The slug can be used to access your post via URL in the webpage, it should be publisher-wide unique.",
|
||||
"attachmentOnDevice": "On-device",
|
||||
"attachmentOnCloud": "On-cloud",
|
||||
"attachments": "Attachments",
|
||||
"publisherCollabInvitation": "Collabration invitations",
|
||||
"publisherCollabInvitationCount": {
|
||||
"zero": "No invitation",
|
||||
"one": "{} available invitation",
|
||||
"other": "{} available invitations"
|
||||
},
|
||||
"failedToLoadUserInfo": "Failed to load user info",
|
||||
"failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.",
|
||||
"failedToLoadUserInfoUnauthorized": "It seems your session has been logged out or not available anymore, you can still try agian to fetch the user info if you want.",
|
||||
"okay": "Okay",
|
||||
"postDetail": "Post Detail",
|
||||
"postCount": {
|
||||
"zero": "No posts",
|
||||
"one": "{} post",
|
||||
"other": "{} posts"
|
||||
},
|
||||
"mimeType": "MIME Type",
|
||||
"fileSize": "File Size",
|
||||
"fileHash": "File Hash",
|
||||
"exifData": "EXIF Data",
|
||||
"postShuffle": "Shuffle Posts",
|
||||
"leveling": "Leveling",
|
||||
"levelingHistory": "Leveling History",
|
||||
"stellarProgram": "Stellar Program",
|
||||
"socialCredits": "Social Credits",
|
||||
"credits": "Credits",
|
||||
"creditsStatus": "Credits Status",
|
||||
"socialCreditsDescription": "Social Credit is a way for Solar Network to evaluate users. It is calculated based on their behavior and interactions. With a base score of 100, higher scores indicate a user's credibility within the community. Scores change over time to reflect a user's recent behavior. Users with higher credit ratings enjoy more benefits, while users with lower credit ratings may have some functionality restricted.",
|
||||
"socialCreditsLevelPoor": "Poor",
|
||||
"socialCreditsLevelNormal": "Normal",
|
||||
"socialCreditsLevelGood": "Good",
|
||||
"socialCreditsLevelExcellent": "Excellent",
|
||||
"orderByPopularity": "Sort by popularity",
|
||||
"orderByReleaseDate": "Sort by release date",
|
||||
"editBot": "Edit Bot",
|
||||
"botAutomatedBy": "Automated by {}",
|
||||
"botDetails": "Bot Details",
|
||||
"overview": "Overview",
|
||||
"keys": "Keys",
|
||||
"botNotFound": "Bot not found.",
|
||||
"newBotKey": "New Bot Key",
|
||||
"newBotKeyHint": "Enter a name for your new key. The key will be shown only once.",
|
||||
"revokeBotKey": "Revoke Bot Key",
|
||||
"revokeBotKeyHint": "Are you sure you want to revoke this key? This action cannot be undone and any application using this key will stop working.",
|
||||
"noBotKeys": "No bot keys yet.",
|
||||
"revoke": "Revoke",
|
||||
"keyName": "Key Name",
|
||||
"newKeyGenerated": "New Key Generated",
|
||||
"copyKeyHint": "Please copy this key and store it somewhere safe. You will not be able to see it again.",
|
||||
"rotateKey": "Rotate Key",
|
||||
"rotateBotKey": "Rotate Bot Key",
|
||||
"rotateBotKeyHint": "Are you sure you want to rotate this key? The old key will become invalid immediately. This action cannot be undone.",
|
||||
"webFeedArticleCount": {
|
||||
"zero": "No articles",
|
||||
"one": "{} article",
|
||||
"other": "{} articles"
|
||||
},
|
||||
"webFeedSubscribed": "The feed has been subscribed",
|
||||
"webFeedUnsubscribed": "The feed has been unsubscribed",
|
||||
"appDetails": "應用程式詳情",
|
||||
"secrets": "密鑰",
|
||||
"appNotFound": "找不到應用程式。",
|
||||
@@ -830,5 +973,107 @@
|
||||
"newSecretGenerated": "已產生新密鑰",
|
||||
"copySecretHint": "請複製此密鑰並將其存放在安全的地方。您將無法再次看到它。",
|
||||
"expiresIn": "過期時間(秒)",
|
||||
"isOidc": "OIDC 相容"
|
||||
}
|
||||
"isOidc": "OIDC 相容",
|
||||
"pinPost": "Pin Post",
|
||||
"unpinPost": "Unpin Post",
|
||||
"pinnedPost": "Pinned",
|
||||
"publisherPage": "Publisher Page",
|
||||
"realmPage": "Realm Page",
|
||||
"replyPage": "Reply Page",
|
||||
"pinPostPublisherHint": "Pin this post to your publisher page",
|
||||
"pinPostRealmHint": "Pin this post to the realm page",
|
||||
"pinPostRealmDisabledHint": "This post doesn't belong to any realm",
|
||||
"pinPostReplyHint": "Pin this post to the reply page",
|
||||
"pinPostReplyDisabledHint": "This post is not a reply",
|
||||
"pin": "Pin",
|
||||
"unpinPostHint": "Are you sure you want to unpin this post?",
|
||||
"all": "All",
|
||||
"statusPresent": "Present",
|
||||
"accountAutomated": "Automated",
|
||||
"chatBreakClearButton": "Clear",
|
||||
"chatBreak5m": "5m",
|
||||
"chatBreak10m": "10m",
|
||||
"chatBreak15m": "15m",
|
||||
"chatBreak30m": "30m",
|
||||
"chatBreakCustomMinutes": "Custom (minutes)",
|
||||
"errorGeneric": "Error: {}",
|
||||
"searchMessages": "Search Messages",
|
||||
"messagesCount": "{} messages",
|
||||
"dotSeparator": "·",
|
||||
"roleValidationHint": "Role must be between 0 and 100",
|
||||
"searchMessagesHint": "Search messages...",
|
||||
"searchLinks": "Links",
|
||||
"searchAttachments": "Attachments",
|
||||
"noMessagesFound": "No messages found",
|
||||
"openInBrowser": "Open in Browser",
|
||||
"highlightPost": "Highlight Post",
|
||||
"filters": "Filters",
|
||||
"apply": "Apply",
|
||||
"pubName": "Pub Name",
|
||||
"realm": "Realm",
|
||||
"shuffle": "Shuffle",
|
||||
"pinned": "Pinned",
|
||||
"noResultsFound": "No results found",
|
||||
"toggleFilters": "Toggle filters",
|
||||
"notableDayNext": "{} is in",
|
||||
"expandPoll": "Expand Poll",
|
||||
"collapsePoll": "Collapse Poll",
|
||||
"embedView": "Embed View",
|
||||
"embedUri": "Embed URI",
|
||||
"aspectRatio": "Aspect Ratio",
|
||||
"renderer": "Renderer",
|
||||
"addEmbed": "Add Embed",
|
||||
"editEmbed": "Edit Embed",
|
||||
"deleteEmbed": "Delete Embed",
|
||||
"deleteEmbedConfirm": "Are you sure you want to delete this embed?",
|
||||
"currentEmbed": "Current Embed",
|
||||
"noEmbed": "No embed yet",
|
||||
"save": "Save",
|
||||
"webView": "Web View",
|
||||
"settingsDefaultPool": "Default file pool",
|
||||
"settingsDefaultPoolHelper": "Select the default storage pool for file uploads",
|
||||
"uploadFile": "Upload File",
|
||||
"authDeviceChallenges": "Device Usage",
|
||||
"authDeviceHint": "Swipe left to edit label, swipe right to logout device.",
|
||||
"settingsMessageDisplayStyle": "Message Display Style",
|
||||
"auto": "Auto",
|
||||
"manual": "Manual",
|
||||
"iframeCode": "Iframe Code",
|
||||
"iframeCodeHint": "<iframe src=\"...\" width=\"...\" height=\"...\">",
|
||||
"parseIframe": "Parse Iframe",
|
||||
"messageActions": "Message Actions",
|
||||
"viewEmbedLoadHint": "Tap to load",
|
||||
"levelingStage1": "Novice",
|
||||
"levelingStage2": "Apprentice",
|
||||
"levelingStage3": "Journeyman",
|
||||
"levelingStage4": "Adept",
|
||||
"levelingStage5": "Expert",
|
||||
"levelingStage6": "Master",
|
||||
"levelingStage7": "Grandmaster",
|
||||
"levelingStage8": "Legend",
|
||||
"levelingStage9": "Myth",
|
||||
"levelingStage10": "Immortal",
|
||||
"levelingStage11": "Divine",
|
||||
"levelingStage12": "Transcendent",
|
||||
"uploadAttachment": "Upload Attachment",
|
||||
"attachmentPreview": "Attachment Preview",
|
||||
"selectPool": "Select Pool",
|
||||
"choosePool": "Choose a pool",
|
||||
"errorLoadingPools": "Error loading pools",
|
||||
"quotaCostInfo": "This upload will cost {} quota points",
|
||||
"uploadConstraints": "Upload Constraints",
|
||||
"fileSizeExceeded": "File size exceeds the maximum limit of {}",
|
||||
"fileTypeNotAccepted": "File type is not accepted by this pool",
|
||||
"files": "Files",
|
||||
"confirmDeleteFile": "Are you sure you want to delete this file?",
|
||||
"deleteFile": "Delete File",
|
||||
"failedToDeleteFile": "Failed to delete file",
|
||||
"drive": "Drive",
|
||||
"allPools": "All Pools",
|
||||
"includeRecycled": "Include Recycled",
|
||||
"confirmDeleteRecycledFiles": "Are you sure you want to delete all recycled files?",
|
||||
"deleteRecycledFiles": "Delete Recycled Files",
|
||||
"recycledFilesDeleted": "Recycled files deleted successfully",
|
||||
"failedToDeleteRecycledFiles": "Failed to delete recycled files",
|
||||
"upload": "Upload"
|
||||
}
|
@@ -8,7 +8,6 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -17,7 +16,7 @@ import 'package:island/firebase_options.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/theme.dart';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/route.dart';
|
||||
@@ -30,6 +29,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
@@ -89,31 +89,42 @@ void main() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
if (!kIsWeb && (Platform.isMacOS || Platform.isLinux || Platform.isWindows)) {
|
||||
doWhenWindowReady(() {
|
||||
const defaultSize = Size(360, 640);
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
// Get saved window size from preferences
|
||||
final savedSizeString = prefs.getString(kAppWindowSize);
|
||||
Size initialSize = defaultSize;
|
||||
const defaultSize = Size(360, 640);
|
||||
|
||||
if (savedSizeString != null) {
|
||||
try {
|
||||
final parts = savedSizeString.split(',');
|
||||
if (parts.length == 2) {
|
||||
final width = double.parse(parts[0]);
|
||||
final height = double.parse(parts[1]);
|
||||
initialSize = Size(width, height);
|
||||
}
|
||||
} catch (e) {
|
||||
log("[SplashScreen] Failed to parse saved window size: $e");
|
||||
initialSize = defaultSize;
|
||||
// Get saved window size from preferences
|
||||
final savedSizeString = prefs.getString(kAppWindowSize);
|
||||
Size initialSize = defaultSize;
|
||||
|
||||
if (savedSizeString != null) {
|
||||
try {
|
||||
final parts = savedSizeString.split(',');
|
||||
if (parts.length == 2) {
|
||||
final width = double.parse(parts[0]);
|
||||
final height = double.parse(parts[1]);
|
||||
initialSize = Size(width, height);
|
||||
}
|
||||
} catch (e) {
|
||||
log("[SplashScreen] Failed to parse saved window size: $e");
|
||||
initialSize = defaultSize;
|
||||
}
|
||||
}
|
||||
|
||||
appWindow.minSize = defaultSize;
|
||||
appWindow.size = initialSize;
|
||||
appWindow.alignment = Alignment.center;
|
||||
appWindow.show();
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: initialSize,
|
||||
center: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
skipTaskbar: false,
|
||||
titleBarStyle: TitleBarStyle.hidden,
|
||||
windowButtonVisibility: true,
|
||||
);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.setMinimumSize(defaultSize);
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
final opacity = prefs.getDouble(kAppWindowOpacity) ?? 1.0;
|
||||
await windowManager.setOpacity(opacity);
|
||||
log(
|
||||
"[SplashScreen] Desktop window is ready with size: ${initialSize.width}x${initialSize.height}",
|
||||
);
|
||||
@@ -235,6 +246,7 @@ class IslandApp extends HookConsumerWidget {
|
||||
final router = ref.watch(routerProvider);
|
||||
|
||||
return MaterialApp.router(
|
||||
color: Colors.transparent,
|
||||
theme: theme?.light,
|
||||
darkTheme: theme?.dark,
|
||||
themeMode: ThemeMode.system,
|
||||
|
@@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/pods/theme.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
part 'config.freezed.dart';
|
||||
part 'config.g.dart';
|
||||
@@ -24,6 +25,7 @@ const kAppDataSavingMode = 'app_data_saving_mode';
|
||||
const kAppSoundEffects = 'app_sound_effects';
|
||||
const kAppAprilFoolFeatures = 'app_april_fool_features';
|
||||
const kAppWindowSize = 'app_window_size';
|
||||
const kAppWindowOpacity = 'app_window_opacity';
|
||||
const kAppEnterToSend = 'app_enter_to_send';
|
||||
const kAppDefaultPoolId = 'app_default_pool_id';
|
||||
const kAppMessageDisplayStyle = 'app_message_display_style';
|
||||
@@ -67,6 +69,7 @@ sealed class AppSettings with _$AppSettings {
|
||||
required String? customFonts,
|
||||
required int? appColorScheme, // The color stored via the int type
|
||||
required Size? windowSize, // The window size for desktop platforms
|
||||
required double windowOpacity, // The window opacity for desktop platforms
|
||||
required String? defaultPoolId,
|
||||
required String messageDisplayStyle,
|
||||
}) = _AppSettings;
|
||||
@@ -88,6 +91,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
||||
customFonts: prefs.getString(kAppCustomFonts),
|
||||
appColorScheme: prefs.getInt(kAppColorSchemeStoreKey),
|
||||
windowSize: _getWindowSizeFromPrefs(prefs),
|
||||
windowOpacity: prefs.getDouble(kAppWindowOpacity) ?? 1.0,
|
||||
defaultPoolId: prefs.getString(kAppDefaultPoolId),
|
||||
messageDisplayStyle: prefs.getString(kAppMessageDisplayStyle) ?? 'bubble',
|
||||
);
|
||||
@@ -196,6 +200,13 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
||||
prefs.setString(kAppMessageDisplayStyle, value);
|
||||
state = state.copyWith(messageDisplayStyle: value);
|
||||
}
|
||||
|
||||
void setWindowOpacity(double value) {
|
||||
final prefs = ref.read(sharedPreferencesProvider);
|
||||
prefs.setDouble(kAppWindowOpacity, value);
|
||||
state = state.copyWith(windowOpacity: value);
|
||||
Future(() => windowManager.setOpacity(value));
|
||||
}
|
||||
}
|
||||
|
||||
final updateInfoProvider =
|
||||
|
@@ -16,6 +16,7 @@ mixin _$AppSettings {
|
||||
|
||||
bool get autoTranslate; bool get dataSavingMode; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; String? get customFonts; int? get appColorScheme;// The color stored via the int type
|
||||
Size? get windowSize;// The window size for desktop platforms
|
||||
double get windowOpacity;// The window opacity for desktop platforms
|
||||
String? get defaultPoolId; String get messageDisplayStyle;
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -27,16 +28,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,defaultPoolId,messageDisplayStyle);
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,windowOpacity,defaultPoolId,messageDisplayStyle);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)';
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, windowOpacity: $windowOpacity, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)';
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +48,7 @@ abstract mixin class $AppSettingsCopyWith<$Res> {
|
||||
factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId, String messageDisplayStyle
|
||||
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle
|
||||
});
|
||||
|
||||
|
||||
@@ -64,7 +65,7 @@ class _$AppSettingsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
||||
as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
||||
@@ -76,7 +77,8 @@ as bool,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundI
|
||||
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable
|
||||
as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
|
||||
as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable
|
||||
as Size?,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
||||
as Size?,windowOpacity: null == windowOpacity ? _self.windowOpacity : windowOpacity // ignore: cast_nullable_to_non_nullable
|
||||
as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
@@ -160,10 +162,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId, String messageDisplayStyle)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettings() when $default != null:
|
||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId,_that.messageDisplayStyle);case _:
|
||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -181,10 +183,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId, String messageDisplayStyle) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettings():
|
||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId,_that.messageDisplayStyle);}
|
||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -198,10 +200,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId, String messageDisplayStyle)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettings() when $default != null:
|
||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId,_that.messageDisplayStyle);case _:
|
||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -213,7 +215,7 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha
|
||||
|
||||
|
||||
class _AppSettings implements AppSettings {
|
||||
const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize, required this.defaultPoolId, required this.messageDisplayStyle});
|
||||
const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize, required this.windowOpacity, required this.defaultPoolId, required this.messageDisplayStyle});
|
||||
|
||||
|
||||
@override final bool autoTranslate;
|
||||
@@ -228,6 +230,8 @@ class _AppSettings implements AppSettings {
|
||||
// The color stored via the int type
|
||||
@override final Size? windowSize;
|
||||
// The window size for desktop platforms
|
||||
@override final double windowOpacity;
|
||||
// The window opacity for desktop platforms
|
||||
@override final String? defaultPoolId;
|
||||
@override final String messageDisplayStyle;
|
||||
|
||||
@@ -241,16 +245,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,defaultPoolId,messageDisplayStyle);
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,windowOpacity,defaultPoolId,messageDisplayStyle);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)';
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, windowOpacity: $windowOpacity, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)';
|
||||
}
|
||||
|
||||
|
||||
@@ -261,7 +265,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith
|
||||
factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId, String messageDisplayStyle
|
||||
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle
|
||||
});
|
||||
|
||||
|
||||
@@ -278,7 +282,7 @@ class __$AppSettingsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) {
|
||||
return _then(_AppSettings(
|
||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
||||
as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
||||
@@ -290,7 +294,8 @@ as bool,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundI
|
||||
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable
|
||||
as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
|
||||
as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable
|
||||
as Size?,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
||||
as Size?,windowOpacity: null == windowOpacity ? _self.windowOpacity : windowOpacity // ignore: cast_nullable_to_non_nullable
|
||||
as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
|
@@ -7,7 +7,7 @@ part of 'config.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$appSettingsNotifierHash() =>
|
||||
r'9f0979f18b107e61185391e7c39bd81ac4b8ca50';
|
||||
r'b5e9b2ea9b01c236a68669a00eaa563c1fb4efa6';
|
||||
|
||||
/// See also [AppSettingsNotifier].
|
||||
@ProviderFor(AppSettingsNotifier)
|
||||
|
@@ -613,6 +613,16 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
_sortMessages([localMessage, ...currentMessages]),
|
||||
);
|
||||
}
|
||||
|
||||
switch (remoteMessage.type) {
|
||||
case "messages.delete":
|
||||
await receiveMessageDeletion(
|
||||
remoteMessage.meta['message_id'] ?? remoteMessage.id,
|
||||
);
|
||||
case "messages.update":
|
||||
case "messages.update.links":
|
||||
await receiveMessageUpdate(remoteMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async {
|
||||
@@ -622,8 +632,12 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
name: 'MessagesNotifier',
|
||||
);
|
||||
|
||||
final targetId = remoteMessage.meta['message_id'] ?? remoteMessage.id;
|
||||
final updatedMessage = LocalChatMessage.fromRemoteMessage(
|
||||
remoteMessage,
|
||||
remoteMessage.copyWith(
|
||||
id: targetId,
|
||||
meta: Map.of(remoteMessage.meta)..remove('message_id'),
|
||||
),
|
||||
MessageStatus.sent,
|
||||
);
|
||||
await _database.updateMessage(_database.messageToCompanion(updatedMessage));
|
||||
|
@@ -1,7 +1,5 @@
|
||||
import "dart:async";
|
||||
import "dart:convert";
|
||||
import "dart:typed_data";
|
||||
import "package:cross_file/cross_file.dart";
|
||||
import "package:easy_localization/easy_localization.dart";
|
||||
import "package:file_picker/file_picker.dart";
|
||||
import "package:flutter/material.dart";
|
||||
@@ -12,9 +10,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:island/database/message.dart";
|
||||
import "package:island/models/chat.dart";
|
||||
import "package:island/models/file.dart";
|
||||
import "package:island/models/file_pool.dart";
|
||||
import "package:island/pods/config.dart";
|
||||
import "package:island/pods/file_pool.dart";
|
||||
import "package:island/pods/messages_notifier.dart";
|
||||
import "package:island/pods/network.dart";
|
||||
import "package:island/pods/websocket.dart";
|
||||
@@ -26,9 +22,7 @@ import "package:island/widgets/app_scaffold.dart";
|
||||
import "package:island/widgets/attachment_uploader.dart";
|
||||
import "package:island/widgets/chat/call_overlay.dart";
|
||||
import "package:island/widgets/chat/message_item.dart";
|
||||
import "package:island/widgets/content/attachment_preview.dart";
|
||||
import "package:island/widgets/content/cloud_files.dart";
|
||||
import "package:island/widgets/content/sheet.dart";
|
||||
import "package:island/widgets/post/compose_shared.dart";
|
||||
import "package:island/widgets/response.dart";
|
||||
import "package:material_symbols_icons/material_symbols_icons.dart";
|
||||
@@ -286,6 +280,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
if (message.chatRoomId != chatRoom.value?.id) return;
|
||||
switch (pkt.type) {
|
||||
case 'messages.new':
|
||||
case 'messages.update':
|
||||
case 'messages.delete':
|
||||
if (message.type.startsWith('call')) {
|
||||
// Handle the ongoing call.
|
||||
ref.invalidate(ongoingCallProvider(message.chatRoomId));
|
||||
@@ -293,14 +289,6 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
messagesNotifier.receiveMessage(message);
|
||||
// Send read receipt for new message
|
||||
sendReadReceipt();
|
||||
case 'messages.update':
|
||||
messagesNotifier.receiveMessageUpdate(message).then((_) {
|
||||
messagesNotifier.receiveMessage(message);
|
||||
});
|
||||
case 'messages.delete':
|
||||
messagesNotifier.receiveMessageDeletion(message.id).then((_) {
|
||||
messagesNotifier.receiveMessage(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +470,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => ChatAttachmentUploaderSheet(
|
||||
(context) => AttachmentUploaderSheet(
|
||||
ref: ref,
|
||||
attachments: attachments.value,
|
||||
index: index,
|
||||
@@ -541,7 +529,10 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
|
||||
SuperListView.builder(
|
||||
listController: listController,
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
padding: EdgeInsets.only(
|
||||
top: 16,
|
||||
bottom: 96 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
controller: scrollController,
|
||||
reverse: true, // Show newest messages at the bottom
|
||||
itemCount: messageList.length,
|
||||
@@ -735,157 +726,160 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: messages.when(
|
||||
data:
|
||||
(messageList) =>
|
||||
messageList.isEmpty
|
||||
? Center(child: Text('No messages yet'.tr()))
|
||||
: chatMessageListWidget(messageList),
|
||||
loading:
|
||||
() => const Center(child: CircularProgressIndicator()),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => messagesNotifier.loadInitial(),
|
||||
),
|
||||
),
|
||||
),
|
||||
chatRoom.when(
|
||||
data:
|
||||
(room) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
switchInCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
switchOutCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, -0.3),
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
// Messages
|
||||
Positioned.fill(
|
||||
child: messages.when(
|
||||
data:
|
||||
(messageList) =>
|
||||
messageList.isEmpty
|
||||
? Center(child: Text('No messages yet'.tr()))
|
||||
: chatMessageListWidget(messageList),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => messagesNotifier.loadInitial(),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Input
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: chatRoom.when(
|
||||
data:
|
||||
(room) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
switchInCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
switchOutCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, -0.3),
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axisAlignment: -1.0,
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axisAlignment: -1.0,
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child:
|
||||
typingStatuses.value.isNotEmpty
|
||||
? Container(
|
||||
key: const ValueKey('typing-indicator'),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.more_horiz,
|
||||
size: 16,
|
||||
).padding(horizontal: 8),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'typingHint'.plural(
|
||||
typingStatuses.value.length,
|
||||
args: [
|
||||
typingStatuses.value
|
||||
.map(
|
||||
(x) =>
|
||||
x.nick ??
|
||||
x.account.nick,
|
||||
)
|
||||
.join(', '),
|
||||
],
|
||||
),
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('typing-indicator-none'),
|
||||
),
|
||||
);
|
||||
},
|
||||
child:
|
||||
typingStatuses.value.isNotEmpty
|
||||
? Container(
|
||||
key: const ValueKey('typing-indicator'),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
),
|
||||
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();
|
||||
}
|
||||
},
|
||||
attachments: attachments.value,
|
||||
onUploadAttachment: uploadAttachment,
|
||||
onDeleteAttachment: (index) async {
|
||||
final attachment = attachments.value[index];
|
||||
if (attachment.isOnCloud) {
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.more_horiz,
|
||||
size: 16,
|
||||
).padding(horizontal: 8),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'typingHint'.plural(
|
||||
typingStatuses.value.length,
|
||||
args: [
|
||||
typingStatuses.value
|
||||
.map(
|
||||
(x) =>
|
||||
x.nick ??
|
||||
x.account.nick,
|
||||
)
|
||||
.join(', '),
|
||||
],
|
||||
),
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('typing-indicator-none'),
|
||||
),
|
||||
),
|
||||
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();
|
||||
}
|
||||
},
|
||||
attachments: attachments.value,
|
||||
onUploadAttachment: uploadAttachment,
|
||||
onDeleteAttachment: (index) async {
|
||||
final attachment = attachments.value[index];
|
||||
if (attachment.isOnCloud) {
|
||||
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(),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
@@ -898,342 +892,3 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatAttachmentUploaderSheet extends StatefulWidget {
|
||||
final WidgetRef ref;
|
||||
final List<UniversalFile> attachments;
|
||||
final int index;
|
||||
|
||||
const ChatAttachmentUploaderSheet({
|
||||
super.key,
|
||||
required this.ref,
|
||||
required this.attachments,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ChatAttachmentUploaderSheet> createState() =>
|
||||
_ChatAttachmentUploaderSheetState();
|
||||
}
|
||||
|
||||
class _ChatAttachmentUploaderSheetState
|
||||
extends State<ChatAttachmentUploaderSheet> {
|
||||
String? selectedPoolId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final attachment = widget.attachments[widget.index];
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: 'uploadAttachment'.tr(),
|
||||
child: FutureBuilder<List<SnFilePool>>(
|
||||
future: widget.ref.read(poolsProvider.future),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('errorLoadingPools'.tr()));
|
||||
}
|
||||
final pools = snapshot.data!;
|
||||
selectedPoolId ??= resolveDefaultPoolId(widget.ref, pools);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedPoolId,
|
||||
items:
|
||||
pools.map((pool) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: pool.id,
|
||||
child: Text(pool.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectedPoolId = value;
|
||||
});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: 'selectPool'.tr(),
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'choosePool'.tr(),
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
FutureBuilder<int?>(
|
||||
future: _getFileSize(attachment),
|
||||
builder: (context, sizeSnapshot) {
|
||||
if (!sizeSnapshot.hasData) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final fileSize = sizeSnapshot.data!;
|
||||
final selectedPool = pools.firstWhere(
|
||||
(p) => p.id == selectedPoolId,
|
||||
);
|
||||
|
||||
// Check file size limit
|
||||
final maxFileSize =
|
||||
selectedPool.policyConfig?['max_file_size']
|
||||
as int?;
|
||||
final fileSizeExceeded =
|
||||
maxFileSize != null && fileSize > maxFileSize;
|
||||
|
||||
// Check accepted types
|
||||
final acceptTypes =
|
||||
selectedPool.policyConfig?['accept_types']
|
||||
as List?;
|
||||
final mimeType =
|
||||
attachment.data.mimeType ??
|
||||
ComposeLogic.getMimeTypeFromFileType(
|
||||
attachment.type,
|
||||
);
|
||||
final typeAccepted =
|
||||
acceptTypes == null ||
|
||||
acceptTypes.isEmpty ||
|
||||
acceptTypes.any(
|
||||
(type) => mimeType.startsWith(type),
|
||||
);
|
||||
|
||||
final hasIssues = fileSizeExceeded || !typeAccepted;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (hasIssues) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.warning,
|
||||
size: 18,
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'uploadConstraints'.tr(),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (fileSizeExceeded) ...[
|
||||
const Gap(4),
|
||||
Text(
|
||||
'fileSizeExceeded'.tr(
|
||||
args: [
|
||||
_formatFileSize(maxFileSize),
|
||||
],
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!typeAccepted) ...[
|
||||
const Gap(4),
|
||||
Text(
|
||||
'fileTypeNotAccepted'.tr(),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
],
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.account_balance_wallet,
|
||||
size: 18,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'quotaCostInfo'.tr(
|
||||
args: [
|
||||
_formatQuotaCost(
|
||||
fileSize,
|
||||
selectedPool,
|
||||
),
|
||||
],
|
||||
),
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium,
|
||||
).fontSize(13),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 4),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(4),
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.info, size: 18),
|
||||
Text(
|
||||
'attachmentPreview'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).fontSize(13),
|
||||
],
|
||||
).padding(horizontal: 4),
|
||||
const Gap(8),
|
||||
AttachmentPreview(item: attachment, isCompact: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Symbols.close),
|
||||
label: Text('cancel').tr(),
|
||||
),
|
||||
const Gap(8),
|
||||
TextButton.icon(
|
||||
onPressed: () => _confirmUpload(),
|
||||
icon: const Icon(Symbols.upload),
|
||||
label: Text('upload').tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<AttachmentUploadConfig?> _getUploadConfig() async {
|
||||
final attachment = widget.attachments[widget.index];
|
||||
final fileSize = await _getFileSize(attachment);
|
||||
|
||||
if (fileSize == null) return null;
|
||||
|
||||
// Get the selected pool to check constraints
|
||||
final pools = await widget.ref.read(poolsProvider.future);
|
||||
final selectedPool = pools.firstWhere((p) => p.id == selectedPoolId);
|
||||
|
||||
// Check constraints
|
||||
final maxFileSize = selectedPool.policyConfig?['max_file_size'] as int?;
|
||||
final fileSizeExceeded = maxFileSize != null && fileSize > maxFileSize;
|
||||
|
||||
final acceptTypes = selectedPool.policyConfig?['accept_types'] as List?;
|
||||
final mimeType =
|
||||
attachment.data.mimeType ??
|
||||
ComposeLogic.getMimeTypeFromFileType(attachment.type);
|
||||
final typeAccepted =
|
||||
acceptTypes == null ||
|
||||
acceptTypes.isEmpty ||
|
||||
acceptTypes.any((type) => mimeType.startsWith(type));
|
||||
|
||||
final hasConstraints = fileSizeExceeded || !typeAccepted;
|
||||
|
||||
return AttachmentUploadConfig(
|
||||
poolId: selectedPoolId!,
|
||||
hasConstraints: hasConstraints,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmUpload() async {
|
||||
final config = await _getUploadConfig();
|
||||
if (config != null && mounted) {
|
||||
Navigator.pop(context, config);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int?> _getFileSize(UniversalFile attachment) async {
|
||||
if (attachment.data is XFile) {
|
||||
try {
|
||||
return await (attachment.data as XFile).length();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
} else if (attachment.data is SnCloudFile) {
|
||||
return (attachment.data as SnCloudFile).size;
|
||||
} else if (attachment.data is List<int>) {
|
||||
return (attachment.data as List<int>).length;
|
||||
} else if (attachment.data is Uint8List) {
|
||||
return (attachment.data as Uint8List).length;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _formatNumber(int number) {
|
||||
if (number >= 1000000) {
|
||||
return '${(number / 1000000).toStringAsFixed(1)}M';
|
||||
} else if (number >= 1000) {
|
||||
return '${(number / 1000).toStringAsFixed(1)}K';
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
||||
|
||||
String _formatFileSize(int bytes) {
|
||||
if (bytes >= 1073741824) {
|
||||
return '${(bytes / 1073741824).toStringAsFixed(1)} GB';
|
||||
} else if (bytes >= 1048576) {
|
||||
return '${(bytes / 1048576).toStringAsFixed(1)} MB';
|
||||
} else if (bytes >= 1024) {
|
||||
return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||
} else {
|
||||
return '$bytes bytes';
|
||||
}
|
||||
}
|
||||
|
||||
String _formatQuotaCost(int fileSize, SnFilePool pool) {
|
||||
final costMultiplier = pool.billingConfig?['cost_multiplier'] ?? 1.0;
|
||||
final quotaCost = ((fileSize / 1024 / 1024) * costMultiplier).round();
|
||||
return _formatNumber(quotaCost);
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
import 'package:island/widgets/post/post_award_sheet.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/widgets/post/post_award_history_sheet.dart';
|
||||
import 'package:island/widgets/post/post_pin_sheet.dart';
|
||||
@@ -273,7 +274,14 @@ class PostActionButtons extends HookConsumerWidget {
|
||||
|
||||
actions.add(
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => PostAwardSheet(post: post),
|
||||
);
|
||||
},
|
||||
onLongPress: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
|
@@ -579,8 +579,33 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
];
|
||||
|
||||
// Desktop-specific settings
|
||||
// But nothing for now
|
||||
final desktopSettings = !isDesktop ? <Widget>[] : <Widget>[];
|
||||
final desktopSettings =
|
||||
!isDesktop
|
||||
? <Widget>[]
|
||||
: [
|
||||
ListTile(
|
||||
minLeadingWidth: 48,
|
||||
title: Text('settingsWindowOpacity').tr(),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
leading: const Icon(Symbols.opacity),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Slider(
|
||||
value: settings.windowOpacity,
|
||||
min: 0.1,
|
||||
max: 1.0,
|
||||
year2023: true,
|
||||
padding: EdgeInsets.only(right: 24),
|
||||
label: '${(settings.windowOpacity * 100).round()}%',
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(appSettingsNotifierProvider.notifier)
|
||||
.setWindowOpacity(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
// Create a responsive layout based on screen width
|
||||
Widget buildSettingsList() {
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class TrayService {
|
||||
TrayService._();
|
||||
@@ -48,15 +47,10 @@ class TrayService {
|
||||
void handleAction(MenuItem item) {
|
||||
switch (item.key) {
|
||||
case 'show_window':
|
||||
() async {
|
||||
appWindow.show();
|
||||
appWindow.restore();
|
||||
await Future.delayed(const Duration(milliseconds: 32));
|
||||
appWindow.show();
|
||||
}();
|
||||
windowManager.show();
|
||||
break;
|
||||
case 'exit_app':
|
||||
appWindow.close();
|
||||
windowManager.destroy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ part of 'status.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$accountStatusHash() => r'c861a0565d6229fd35666bba7cb2f5c6b7298e46';
|
||||
String _$accountStatusHash() => r'abc2f11f0fbaf637efc182cf85ab838936c4d875';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -15,6 +14,7 @@ import 'package:island/services/responsive.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class AppScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
@@ -36,11 +36,12 @@ class WindowScaffold extends HookConsumerWidget {
|
||||
if (!kIsWeb &&
|
||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||
void saveWindowSize() {
|
||||
final size = appWindow.size;
|
||||
final settingsNotifier = ref.read(
|
||||
appSettingsNotifierProvider.notifier,
|
||||
);
|
||||
settingsNotifier.setWindowSize(size);
|
||||
windowManager.getBounds().then((bounds) {
|
||||
final settingsNotifier = ref.read(
|
||||
appSettingsNotifierProvider.notifier,
|
||||
);
|
||||
settingsNotifier.setWindowSize(bounds.size);
|
||||
});
|
||||
}
|
||||
|
||||
// Save window size when app is about to close
|
||||
@@ -61,13 +62,6 @@ class WindowScaffold extends HookConsumerWidget {
|
||||
if (!kIsWeb &&
|
||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
final windowButtonColor = WindowButtonColors(
|
||||
iconNormal: Theme.of(context).colorScheme.primary,
|
||||
mouseOver: Theme.of(context).colorScheme.primaryContainer,
|
||||
mouseDown: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
iconMouseOver: Theme.of(context).colorScheme.primary,
|
||||
iconMouseDown: Theme.of(context).colorScheme.primary,
|
||||
);
|
||||
|
||||
return Material(
|
||||
child: Stack(
|
||||
@@ -75,44 +69,66 @@ class WindowScaffold extends HookConsumerWidget {
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
WindowTitleBarBox(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1 / devicePixelRatio,
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1 / devicePixelRatio,
|
||||
),
|
||||
),
|
||||
child: MoveWindow(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment:
|
||||
Platform.isMacOS
|
||||
? MainAxisAlignment.center
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Solar Network',
|
||||
textAlign:
|
||||
Platform.isMacOS
|
||||
? TextAlign.center
|
||||
: TextAlign.start,
|
||||
).padding(horizontal: 12, vertical: 5),
|
||||
),
|
||||
child: DragToMoveArea(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment:
|
||||
Platform.isMacOS
|
||||
? MainAxisAlignment.center
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Solar Network',
|
||||
textAlign:
|
||||
Platform.isMacOS
|
||||
? TextAlign.center
|
||||
: TextAlign.start,
|
||||
).padding(horizontal: 12, vertical: 5),
|
||||
),
|
||||
if (!Platform.isMacOS)
|
||||
IconButton(
|
||||
icon: Icon(Symbols.minimize),
|
||||
onPressed: () => windowManager.minimize(),
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
if (!Platform.isMacOS)
|
||||
MinimizeWindowButton(colors: windowButtonColor),
|
||||
if (!Platform.isMacOS)
|
||||
MaximizeWindowButton(colors: windowButtonColor),
|
||||
if (!Platform.isMacOS)
|
||||
CloseWindowButton(
|
||||
colors: windowButtonColor,
|
||||
onPressed: () => appWindow.hide(),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!Platform.isMacOS)
|
||||
IconButton(
|
||||
icon: Icon(Symbols.maximize),
|
||||
onPressed: () async {
|
||||
if (await windowManager.isMaximized()) {
|
||||
windowManager.restore();
|
||||
} else {
|
||||
windowManager.maximize();
|
||||
}
|
||||
},
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
if (!Platform.isMacOS)
|
||||
IconButton(
|
||||
icon: Icon(Symbols.close),
|
||||
onPressed: () => windowManager.close(),
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -12,6 +11,7 @@ import 'package:island/services/update_service.dart';
|
||||
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||
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 {
|
||||
final Widget child;
|
||||
@@ -67,11 +67,7 @@ class AppWrapper extends HookConsumerWidget with TrayListener {
|
||||
}
|
||||
|
||||
void _trayIconPrimaryAction() {
|
||||
if (appWindow.isVisible) {
|
||||
appWindow.restore();
|
||||
} else {
|
||||
appWindow.show();
|
||||
}
|
||||
windowManager.show();
|
||||
}
|
||||
|
||||
void _trayIconSecondaryAction() {
|
||||
|
@@ -26,15 +26,20 @@ class AttachmentUploadConfig {
|
||||
|
||||
class AttachmentUploaderSheet extends StatefulWidget {
|
||||
final WidgetRef ref;
|
||||
final ComposeState state;
|
||||
final ComposeState? state;
|
||||
final List<UniversalFile>? attachments;
|
||||
final int index;
|
||||
|
||||
const AttachmentUploaderSheet({
|
||||
super.key,
|
||||
required this.ref,
|
||||
required this.state,
|
||||
this.state,
|
||||
this.attachments,
|
||||
required this.index,
|
||||
});
|
||||
}) : assert(
|
||||
state != null || attachments != null,
|
||||
'Either state or attachments must be provided',
|
||||
);
|
||||
|
||||
@override
|
||||
State<AttachmentUploaderSheet> createState() =>
|
||||
@@ -46,7 +51,9 @@ class _AttachmentUploaderSheetState extends State<AttachmentUploaderSheet> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final attachment = widget.state.attachments.value[widget.index];
|
||||
final attachment =
|
||||
widget.attachments?[widget.index] ??
|
||||
widget.state!.attachments.value[widget.index];
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: 'uploadAttachment'.tr(),
|
||||
@@ -111,19 +118,18 @@ class _AttachmentUploaderSheetState extends State<AttachmentUploaderSheet> {
|
||||
|
||||
// Check accepted types
|
||||
final acceptTypes =
|
||||
selectedPool.policyConfig?['accept_types']
|
||||
as List?;
|
||||
(selectedPool.policyConfig?['accept_types']
|
||||
as List?)
|
||||
?.cast<String>();
|
||||
final mimeType =
|
||||
attachment.data.mimeType ??
|
||||
ComposeLogic.getMimeTypeFromFileType(
|
||||
attachment.type,
|
||||
);
|
||||
final typeAccepted =
|
||||
acceptTypes == null ||
|
||||
acceptTypes.isEmpty ||
|
||||
acceptTypes.any(
|
||||
(type) => mimeType.startsWith(type),
|
||||
);
|
||||
final typeAccepted = _isMimeTypeAccepted(
|
||||
mimeType,
|
||||
acceptTypes,
|
||||
);
|
||||
|
||||
final hasIssues = fileSizeExceeded || !typeAccepted;
|
||||
|
||||
@@ -279,7 +285,9 @@ class _AttachmentUploaderSheetState extends State<AttachmentUploaderSheet> {
|
||||
}
|
||||
|
||||
Future<AttachmentUploadConfig?> _getUploadConfig() async {
|
||||
final attachment = widget.state.attachments.value[widget.index];
|
||||
final attachment =
|
||||
widget.attachments?[widget.index] ??
|
||||
widget.state!.attachments.value[widget.index];
|
||||
final fileSize = await _getFileSize(attachment);
|
||||
|
||||
if (fileSize == null) return null;
|
||||
@@ -292,14 +300,12 @@ class _AttachmentUploaderSheetState extends State<AttachmentUploaderSheet> {
|
||||
final maxFileSize = selectedPool.policyConfig?['max_file_size'] as int?;
|
||||
final fileSizeExceeded = maxFileSize != null && fileSize > maxFileSize;
|
||||
|
||||
final acceptTypes = selectedPool.policyConfig?['accept_types'] as List?;
|
||||
final acceptTypes =
|
||||
(selectedPool.policyConfig?['accept_types'] as List?)?.cast<String>();
|
||||
final mimeType =
|
||||
attachment.data.mimeType ??
|
||||
ComposeLogic.getMimeTypeFromFileType(attachment.type);
|
||||
final typeAccepted =
|
||||
acceptTypes == null ||
|
||||
acceptTypes.isEmpty ||
|
||||
acceptTypes.any((type) => mimeType.startsWith(type));
|
||||
final typeAccepted = _isMimeTypeAccepted(mimeType, acceptTypes);
|
||||
|
||||
final hasConstraints = fileSizeExceeded || !typeAccepted;
|
||||
|
||||
@@ -360,4 +366,16 @@ class _AttachmentUploaderSheetState extends State<AttachmentUploaderSheet> {
|
||||
final quotaCost = ((fileSize / 1024 / 1024) * costMultiplier).round();
|
||||
return _formatNumber(quotaCost);
|
||||
}
|
||||
|
||||
bool _isMimeTypeAccepted(String mimeType, List<String>? acceptTypes) {
|
||||
if (acceptTypes == null || acceptTypes.isEmpty) return true;
|
||||
return acceptTypes.any((type) {
|
||||
if (type.endsWith('/*')) {
|
||||
final mainType = type.substring(0, type.length - 2);
|
||||
return mimeType.startsWith('$mainType/');
|
||||
} else {
|
||||
return mimeType == type;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
import "package:easy_localization/easy_localization.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
@@ -56,10 +54,6 @@ class ChatInput extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final inputFocusNode = useFocusNode();
|
||||
|
||||
final enterToSend = ref.watch(appSettingsNotifierProvider).enterToSend;
|
||||
|
||||
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
||||
|
||||
void send() {
|
||||
onSend.call();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -67,6 +61,18 @@ class ChatInput extends HookConsumerWidget {
|
||||
});
|
||||
}
|
||||
|
||||
void insertNewLine() {
|
||||
final text = messageController.text;
|
||||
final selection = messageController.selection;
|
||||
final start = selection.start >= 0 ? selection.start : text.length;
|
||||
final end = selection.end >= 0 ? selection.end : text.length;
|
||||
final newText = text.replaceRange(start, end, '\n');
|
||||
messageController.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection: TextSelection.collapsed(offset: start + 1),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> handlePaste() async {
|
||||
final clipboard = await Pasteboard.image;
|
||||
if (clipboard == null) return;
|
||||
@@ -80,212 +86,203 @@ class ChatInput extends HookConsumerWidget {
|
||||
]);
|
||||
}
|
||||
|
||||
void handleKeyPress(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
RawKeyEvent event,
|
||||
) {
|
||||
if (event is! RawKeyDownEvent) return;
|
||||
inputFocusNode.onKeyEvent = (node, event) {
|
||||
if (event is! KeyDownEvent) return KeyEventResult.ignored;
|
||||
|
||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
||||
final isModifierPressed =
|
||||
HardwareKeyboard.instance.isMetaPressed ||
|
||||
HardwareKeyboard.instance.isControlPressed;
|
||||
|
||||
if (isPaste && isModifierPressed) {
|
||||
handlePaste();
|
||||
return;
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
final enterToSend = ref.read(appSettingsNotifierProvider).enterToSend;
|
||||
final isEnter = event.logicalKey == LogicalKeyboardKey.enter;
|
||||
|
||||
if (isEnter) {
|
||||
if (enterToSend && !isModifierPressed) {
|
||||
send();
|
||||
} else if (!enterToSend && isModifierPressed) {
|
||||
if (isModifierPressed) {
|
||||
insertNewLine();
|
||||
return KeyEventResult.handled;
|
||||
} else if (enterToSend) {
|
||||
send();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Material(
|
||||
elevation: 8,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
if (attachments.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 280,
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
return SizedBox(
|
||||
height: 280,
|
||||
width: 280,
|
||||
child: AttachmentPreview(
|
||||
item: attachments[idx],
|
||||
progress: attachmentProgress['chat-upload']?[idx],
|
||||
onRequestUpload: () => onUploadAttachment(idx),
|
||||
onDelete: () => onDeleteAttachment(idx),
|
||||
onUpdate: (value) {
|
||||
attachments[idx] = value;
|
||||
onAttachmentsChanged(attachments);
|
||||
},
|
||||
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, _) => const Gap(8),
|
||||
),
|
||||
).padding(top: 12),
|
||||
if (messageReplyingTo != null ||
|
||||
messageForwardingTo != null ||
|
||||
messageEditingTo != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
margin: const EdgeInsets.only(left: 8, right: 8, top: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
messageReplyingTo != null
|
||||
? Symbols.reply
|
||||
: messageForwardingTo != null
|
||||
? Symbols.forward
|
||||
: Symbols.edit,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
messageReplyingTo != null
|
||||
? 'Replying to ${messageReplyingTo?.sender.account.nick}'
|
||||
: messageForwardingTo != null
|
||||
? 'Forwarding message'
|
||||
: 'Editing message',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, size: 20),
|
||||
onPressed: onClear,
|
||||
padding: EdgeInsets.zero,
|
||||
style: ButtonStyle(
|
||||
minimumSize: WidgetStatePropertyAll(Size(28, 28)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'stickers'.tr(),
|
||||
icon: const Icon(Symbols.add_reaction),
|
||||
onPressed: () {
|
||||
final size = MediaQuery.of(context).size;
|
||||
showStickerPickerPopover(
|
||||
context,
|
||||
Offset(
|
||||
20,
|
||||
size.height -
|
||||
480 -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
onPick: (placeholder) {
|
||||
// Insert placeholder at current cursor position
|
||||
final text = messageController.text;
|
||||
final selection = messageController.selection;
|
||||
final start =
|
||||
selection.start >= 0
|
||||
? selection.start
|
||||
: text.length;
|
||||
final end =
|
||||
selection.end >= 0
|
||||
? selection.end
|
||||
: text.length;
|
||||
final newText = text.replaceRange(
|
||||
start,
|
||||
end,
|
||||
placeholder,
|
||||
);
|
||||
messageController.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection: TextSelection.collapsed(
|
||||
offset: start + placeholder.length,
|
||||
),
|
||||
);
|
||||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
if (attachments.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
return SizedBox(
|
||||
width: 180,
|
||||
child: AttachmentPreview(
|
||||
isCompact: true,
|
||||
item: attachments[idx],
|
||||
progress: attachmentProgress['chat-upload']?[idx],
|
||||
onRequestUpload: () => onUploadAttachment(idx),
|
||||
onDelete: () => onDeleteAttachment(idx),
|
||||
onUpdate: (value) {
|
||||
attachments[idx] = value;
|
||||
onAttachmentsChanged(attachments);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Symbols.photo_library),
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(true),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.photo),
|
||||
Text('addPhoto').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(false),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.video_call),
|
||||
Text('addVideo').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, _) => const Gap(8),
|
||||
),
|
||||
).padding(top: 12),
|
||||
if (messageReplyingTo != null ||
|
||||
messageForwardingTo != null ||
|
||||
messageEditingTo != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 8,
|
||||
bottom: 4,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
messageReplyingTo != null
|
||||
? Symbols.reply
|
||||
: messageForwardingTo != null
|
||||
? Symbols.forward
|
||||
: Symbols.edit,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
messageReplyingTo != null
|
||||
? 'Replying to ${messageReplyingTo?.sender.account.nick}'
|
||||
: messageForwardingTo != null
|
||||
? 'Forwarding message'
|
||||
: 'Editing message',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: InkWell(
|
||||
onTap: onClear,
|
||||
child: const Icon(Icons.close, size: 20).center(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey: (event) => handleKeyPress(context, ref, event),
|
||||
Row(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'stickers'.tr(),
|
||||
icon: const Icon(Symbols.add_reaction),
|
||||
onPressed: () {
|
||||
final size = MediaQuery.of(context).size;
|
||||
showStickerPickerPopover(
|
||||
context,
|
||||
Offset(
|
||||
20,
|
||||
size.height -
|
||||
480 -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
onPick: (placeholder) {
|
||||
// Insert placeholder at current cursor position
|
||||
final text = messageController.text;
|
||||
final selection = messageController.selection;
|
||||
final start =
|
||||
selection.start >= 0
|
||||
? selection.start
|
||||
: text.length;
|
||||
final end =
|
||||
selection.end >= 0
|
||||
? selection.end
|
||||
: text.length;
|
||||
final newText = text.replaceRange(
|
||||
start,
|
||||
end,
|
||||
placeholder,
|
||||
);
|
||||
messageController.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection: TextSelection.collapsed(
|
||||
offset: start + placeholder.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Symbols.photo_library),
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(true),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.photo),
|
||||
Text('addPhoto').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(false),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.video_call),
|
||||
Text('addVideo').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
focusNode: inputFocusNode,
|
||||
controller: messageController,
|
||||
onSubmitted:
|
||||
(enterToSend && isMobile)
|
||||
? (_) {
|
||||
send();
|
||||
}
|
||||
: null,
|
||||
keyboardType:
|
||||
(enterToSend && isMobile)
|
||||
? TextInputType.text
|
||||
: TextInputType.multiline,
|
||||
textInputAction: TextInputAction.send,
|
||||
inputFormatters: [
|
||||
if (enterToSend && !isMobile)
|
||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||
if (newValue.text.endsWith('\n')) {
|
||||
return oldValue;
|
||||
}
|
||||
return newValue;
|
||||
}),
|
||||
],
|
||||
keyboardType: TextInputType.multiline,
|
||||
decoration: InputDecoration(
|
||||
hintText:
|
||||
(chatRoom.type == 1 && chatRoom.name == null)
|
||||
@@ -314,16 +311,16 @@ class ChatInput extends HookConsumerWidget {
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.send),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: send,
|
||||
),
|
||||
],
|
||||
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.send),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: send,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ class MessageContent extends StatelessWidget {
|
||||
case 'messages.update.links':
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.edit,
|
||||
@@ -64,27 +64,29 @@ class MessageContent extends StatelessWidget {
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
),
|
||||
).padding(top: 2),
|
||||
const Gap(4),
|
||||
if (item.meta['previous_content'] is String)
|
||||
PrettyDiffText(
|
||||
oldText: item.meta['previous_content'],
|
||||
newText: item.content ?? 'Edited a message',
|
||||
defaultTextStyle: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
addedTextStyle: TextStyle(
|
||||
backgroundColor: Theme.of(
|
||||
Flexible(
|
||||
child: PrettyDiffText(
|
||||
oldText: item.meta['previous_content'],
|
||||
newText: item.content ?? 'Edited a message',
|
||||
defaultTextStyle: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryFixedDim.withOpacity(0.4),
|
||||
),
|
||||
deletedTextStyle: TextStyle(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.7),
|
||||
).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
addedTextStyle: TextStyle(
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryFixedDim.withOpacity(0.4),
|
||||
),
|
||||
deletedTextStyle: TextStyle(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
@@ -104,10 +106,12 @@ class MessageContent extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MarkdownTextContent(
|
||||
content: item.content ?? '*${item.type} has no content*',
|
||||
isSelectable: true,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
Flexible(
|
||||
child: MarkdownTextContent(
|
||||
content: item.content ?? '*${item.type} has no content*',
|
||||
isSelectable: true,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
if (translatedText?.isNotEmpty ?? false)
|
||||
...([
|
||||
|
@@ -538,7 +538,7 @@ class MessageItemDisplayIRC extends HookConsumerWidget {
|
||||
isMultiline ? CrossAxisAlignment.start : CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('HH:mm').format(message.createdAt),
|
||||
DateFormat('HH:mm').format(message.createdAt.toLocal()),
|
||||
style: TextStyle(color: textColor.withOpacity(0.7), fontSize: 12),
|
||||
).padding(top: isMultiline ? 2 : 0),
|
||||
AccountPfcGestureDetector(
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -80,9 +81,8 @@ class PostItemCreator extends HookConsumerWidget {
|
||||
title: 'copyLink'.tr(),
|
||||
image: MenuImage.icon(Symbols.link),
|
||||
callback: () {
|
||||
context.pushNamed(
|
||||
'postDetail',
|
||||
pathParameters: {'id': item.id},
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: 'https://solian.app/posts/${item.id}'),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -95,7 +95,7 @@ class PostItemCreator extends HookConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () {
|
||||
if (isOpenable) {
|
||||
context.goNamed('postDetail', pathParameters: {'id': item.id});
|
||||
context.pushNamed('postDetail', pathParameters: {'id': item.id});
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
|
@@ -6,7 +6,6 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||
#include <file_saver/file_saver_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_platform_alert/flutter_platform_alert_plugin.h>
|
||||
@@ -20,16 +19,15 @@
|
||||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <record_linux/record_linux_plugin.h>
|
||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <super_native_extensions/super_native_extensions_plugin.h>
|
||||
#include <tray_manager/tray_manager_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <volume_controller/volume_controller_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
||||
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_saver_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
|
||||
file_saver_plugin_register_with_registrar(file_saver_registrar);
|
||||
@@ -69,6 +67,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) record_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
|
||||
record_linux_plugin_register_with_registrar(record_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
|
||||
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||
@@ -84,4 +85,7 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) volume_controller_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "VolumeControllerPlugin");
|
||||
volume_controller_plugin_register_with_registrar(volume_controller_registrar);
|
||||
g_autoptr(FlPluginRegistrar) window_manager_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
|
||||
window_manager_plugin_register_with_registrar(window_manager_registrar);
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_linux
|
||||
file_saver
|
||||
file_selector_linux
|
||||
flutter_platform_alert
|
||||
@@ -17,11 +16,13 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
media_kit_video
|
||||
pasteboard
|
||||
record_linux
|
||||
screen_retriever_linux
|
||||
sqlite3_flutter_libs
|
||||
super_native_extensions
|
||||
tray_manager
|
||||
url_launcher_linux
|
||||
volume_controller
|
||||
window_manager
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
@@ -6,19 +6,20 @@
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||
|
||||
struct _MyApplication {
|
||||
struct _MyApplication
|
||||
{
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
char **dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
static void my_application_activate(GApplication *application)
|
||||
{
|
||||
MyApplication *self = MY_APPLICATION(application);
|
||||
GtkWindow *window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
@@ -30,32 +31,36 @@ static void my_application_activate(GApplication* application) {
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
GdkScreen *screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen))
|
||||
{
|
||||
const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0)
|
||||
{
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
if (use_header_bar)
|
||||
{
|
||||
GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "island");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_window_set_title(window, "island");
|
||||
}
|
||||
|
||||
auto bdw = bitsdojo_window_from(window);
|
||||
bdw->setCustomFrame(true);
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
FlView *view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
@@ -65,16 +70,18 @@ static void my_application_activate(GApplication* application) {
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status)
|
||||
{
|
||||
MyApplication *self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
if (!g_application_register(application, nullptr, &error))
|
||||
{
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
@@ -84,8 +91,9 @@ static gboolean my_application_local_command_line(GApplication* application, gch
|
||||
}
|
||||
|
||||
// Implements GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
static void my_application_startup(GApplication *application)
|
||||
{
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
@@ -93,8 +101,9 @@ static void my_application_startup(GApplication* application) {
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
static void my_application_shutdown(GApplication *application)
|
||||
{
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
@@ -102,13 +111,15 @@ static void my_application_shutdown(GApplication* application) {
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
static void my_application_dispose(GObject *object)
|
||||
{
|
||||
MyApplication *self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
static void my_application_class_init(MyApplicationClass *klass)
|
||||
{
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
@@ -116,9 +127,10 @@ static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
static void my_application_init(MyApplication *self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
MyApplication *my_application_new()
|
||||
{
|
||||
// Set the program name to the application ID, which helps various systems
|
||||
// like GTK and desktop environments map this running application to its
|
||||
// corresponding .desktop file. This ensures better integration by allowing
|
||||
|
@@ -5,7 +5,6 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import bitsdojo_window_macos
|
||||
import connectivity_plus
|
||||
import device_info_plus
|
||||
import file_picker
|
||||
@@ -32,6 +31,7 @@ import package_info_plus
|
||||
import pasteboard
|
||||
import path_provider_foundation
|
||||
import record_macos
|
||||
import screen_retriever_macos
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import sign_in_with_apple
|
||||
@@ -42,9 +42,9 @@ import tray_manager
|
||||
import url_launcher_macos
|
||||
import volume_controller
|
||||
import wakelock_plus
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
@@ -71,6 +71,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
|
||||
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
|
||||
@@ -81,4 +82,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin"))
|
||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
}
|
||||
|
@@ -1,6 +1,4 @@
|
||||
PODS:
|
||||
- bitsdojo_window_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- connectivity_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- croppy (0.0.1):
|
||||
@@ -202,6 +200,8 @@ PODS:
|
||||
- record_macos (1.1.0):
|
||||
- FlutterMacOS
|
||||
- SAMKeychain (1.5.3)
|
||||
- screen_retriever_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -248,9 +248,10 @@ PODS:
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (137.7151.04)
|
||||
- window_manager (0.5.0):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/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`)
|
||||
@@ -279,6 +280,7 @@ DEPENDENCIES:
|
||||
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- 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`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sign_in_with_apple (from `Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos`)
|
||||
@@ -289,6 +291,7 @@ DEPENDENCIES:
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@@ -314,8 +317,6 @@ SPEC REPOS:
|
||||
- WebRTC-SDK
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
bitsdojo_window_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
|
||||
connectivity_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
|
||||
croppy:
|
||||
@@ -372,6 +373,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
record_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos
|
||||
screen_retriever_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
|
||||
share_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
shared_preferences_foundation:
|
||||
@@ -392,9 +395,10 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/volume_controller/macos
|
||||
wakelock_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
window_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9
|
||||
connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e
|
||||
croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
@@ -441,6 +445,7 @@ SPEC CHECKSUMS:
|
||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||
record_macos: 43194b6c06ca6f8fa132e2acea72b202b92a0f5b
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
|
||||
@@ -453,6 +458,7 @@ SPEC CHECKSUMS:
|
||||
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
||||
window_manager: b729e31d38fb04905235df9ea896128991cad99e
|
||||
|
||||
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
||||
|
||||
|
@@ -4,10 +4,25 @@ import FlutterMacOS
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool)
|
||||
-> Bool
|
||||
{
|
||||
if !flag {
|
||||
for window in NSApp.windows {
|
||||
if !window.isVisible {
|
||||
window.setIsVisible(true)
|
||||
}
|
||||
window.makeKeyAndOrderFront(self)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
import bitsdojo_window_macos
|
||||
|
||||
class MainFlutterWindow: BitsdojoWindow {
|
||||
override func bitsdojo_window_configure() -> UInt {
|
||||
return BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP
|
||||
}
|
||||
|
||||
class MainFlutterWindow: NSWindow {
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
@@ -17,4 +12,4 @@ class MainFlutterWindow: BitsdojoWindow {
|
||||
|
||||
super.awakeFromNib()
|
||||
}
|
||||
}
|
||||
}
|
92
pubspec.lock
92
pubspec.lock
@@ -81,46 +81,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
bitsdojo_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bitsdojo_window
|
||||
sha256: "88ef7765dafe52d97d7a3684960fb5d003e3151e662c18645c1641c22b873195"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
bitsdojo_window_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_linux
|
||||
sha256: "9519c0614f98be733e0b1b7cb15b827007886f6fe36a4fb62cf3d35b9dd578ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
bitsdojo_window_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_macos
|
||||
sha256: f7c5be82e74568c68c5b8449e2c5d8fd12ec195ecd70745a7b9c0f802bb0268f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
bitsdojo_window_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_platform_interface
|
||||
sha256: "65daa015a0c6dba749bdd35a0f092e7a8ba8b0766aa0480eb3ef808086f6e27c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
bitsdojo_window_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_windows
|
||||
sha256: fa982cf61ede53f483e50b257344a1c250af231a3cdc93a7064dd6dc0d720b68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1201,10 +1161,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: ebc94ed30fd13cefd397cb1658b593f21571f014b7d1197eeb41fb95f05d899a
|
||||
sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.3.2"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2125,6 +2085,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever
|
||||
sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
screen_retriever_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_linux
|
||||
sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
screen_retriever_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_macos
|
||||
sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
screen_retriever_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_platform_interface
|
||||
sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
screen_retriever_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_windows
|
||||
sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
screenshot:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2796,6 +2796,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: window_manager
|
||||
sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
windows_notification:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@@ -38,7 +38,6 @@ dependencies:
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_hooks: ^0.21.3+1
|
||||
hooks_riverpod: ^2.6.1
|
||||
bitsdojo_window: ^0.1.6
|
||||
go_router: ^16.2.4
|
||||
styled_widget: ^0.4.1
|
||||
shared_preferences: ^2.5.3
|
||||
@@ -53,7 +52,7 @@ dependencies:
|
||||
flutter_highlight: ^0.7.0
|
||||
uuid: ^4.5.1
|
||||
url_launcher: ^6.3.2
|
||||
google_fonts: ^6.3.1
|
||||
google_fonts: ^6.3.2
|
||||
gap: ^3.0.1
|
||||
cached_network_image: ^3.4.1
|
||||
web: ^1.1.1
|
||||
@@ -153,6 +152,7 @@ dependencies:
|
||||
ffi: ^2.1.4
|
||||
dart_ipc: ^1.0.1
|
||||
pretty_diff_text: ^2.1.0
|
||||
window_manager: ^0.5.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@@ -6,7 +6,6 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_windows/bitsdojo_window_plugin.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>
|
||||
@@ -26,17 +25,17 @@
|
||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||
#include <pasteboard/pasteboard_plugin.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>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <super_native_extensions/super_native_extensions_plugin_c_api.h>
|
||||
#include <tray_manager/tray_manager_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <volume_controller/volume_controller_plugin_c_api.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
#include <windows_notification/windows_notification_plugin_c_api.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
BitsdojoWindowPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
DartIpcPluginCApiRegisterWithRegistrar(
|
||||
@@ -75,6 +74,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("PasteboardPlugin"));
|
||||
RecordWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
|
||||
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
|
||||
@@ -87,6 +88,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
VolumeControllerPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("VolumeControllerPluginCApi"));
|
||||
WindowManagerPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
|
||||
WindowsNotificationPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("WindowsNotificationPluginCApi"));
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_windows
|
||||
connectivity_plus
|
||||
dart_ipc
|
||||
file_saver
|
||||
@@ -23,12 +22,14 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
media_kit_video
|
||||
pasteboard
|
||||
record_windows
|
||||
screen_retriever_windows
|
||||
share_plus
|
||||
sqlite3_flutter_libs
|
||||
super_native_extensions
|
||||
tray_manager
|
||||
url_launcher_windows
|
||||
volume_controller
|
||||
window_manager
|
||||
windows_notification
|
||||
)
|
||||
|
||||
|
@@ -5,14 +5,21 @@
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
||||
auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
_In_ wchar_t *command_line, _In_ int show_command)
|
||||
{
|
||||
HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", L"single_instance_example");
|
||||
if (hwnd != NULL)
|
||||
{
|
||||
::ShowWindow(hwnd, SW_NORMAL);
|
||||
::SetForegroundWindow(hwnd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())
|
||||
{
|
||||
CreateAndAttachConsole();
|
||||
}
|
||||
|
||||
@@ -30,13 +37,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.Create(L"Solian", origin, size)) {
|
||||
if (!window.Create(L"Solian", origin, size))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
||||
::MSG msg;
|
||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||
while (::GetMessage(&msg, nullptr, 0, 0))
|
||||
{
|
||||
::TranslateMessage(&msg);
|
||||
::DispatchMessage(&msg);
|
||||
}
|
||||
|
Reference in New Issue
Block a user