Compare commits

...

6 Commits

31 changed files with 7410 additions and 2527 deletions

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}

View File

@@ -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,

View File

@@ -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 =

View File

@@ -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,
));

View File

@@ -7,7 +7,7 @@ part of 'config.dart';
// **************************************************************************
String _$appSettingsNotifierHash() =>
r'9f0979f18b107e61185391e7c39bd81ac4b8ca50';
r'b5e9b2ea9b01c236a68669a00eaa563c1fb4efa6';
/// See also [AppSettingsNotifier].
@ProviderFor(AppSettingsNotifier)

View File

@@ -541,7 +541,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 +738,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,

View File

@@ -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() {

View File

@@ -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;
}
}

View File

@@ -6,7 +6,7 @@ part of 'status.dart';
// RiverpodGenerator
// **************************************************************************
String _$accountStatusHash() => r'c861a0565d6229fd35666bba7cb2f5c6b7298e46';
String _$accountStatusHash() => r'abc2f11f0fbaf637efc182cf85ab838936c4d875';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -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,
),
],
),
),
),

View File

@@ -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() {

View File

@@ -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: 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);
},
);
},
),
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,
),
],
),
],
),
],
),
),
);
}

View File

@@ -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)
...([

View File

@@ -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(

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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"))
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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:

View File

@@ -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:

View File

@@ -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"));
}

View File

@@ -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
)

View File

@@ -9,10 +9,20 @@
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 +40,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);
}