diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 5123be44..d517369b 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -32,6 +32,7 @@ "fieldEmailAddressMustBeValid": "The email address must be valid.", "logout": "Logout", "updateYourProfile": "Profile Settings", + "updateYourProfileDescription": "Adjust how you looks on the Solar Network.", "accountBasicInfo": "Basic Info", "accountProfile": "Your Profile", "saveChanges": "Save Changes", @@ -69,15 +70,18 @@ "authFactorPin": "Pin Code", "authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.", "realms": "Realms", + "realmsDescription": "Manage realms you've joined.", "createRealm": "Create a Realm", "createRealmHint": "Meet friends with same interests, build communities, and more.", "editRealm": "Edit Realm", "deleteRealm": "Delete Realm", "deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.", "explore": "Explore", + "exploreDescription": "Explore contents on the Solar Network.", "exploreFilterSubscriptions": "Subscriptions", "exploreFilterFriends": "Friends", "account": "Account", + "accountDescription": "Information about your account.", "name": "Name", "slug": "Slug", "slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.", @@ -86,6 +90,7 @@ "deleteChatRoom": "Delete Room", "deleteChatRoomHint": "Are you sure to delete this room? This action cannot be undone.", "chat": "Chat", + "chatDescription": "Group Chats and Direct Messages", "chatTabAll": "All", "chatTabDirect": "Direct Messages", "chatTabGroup": "Group Chats", @@ -192,7 +197,9 @@ "statusActivityTitle": "{} is {} {}", "statusActivityEndedTitle": "{} is {} {} until {}", "appSettings": "App Settings", + "appSettingsDescription": "Customize your app.", "accountSettings": "Account Settings", + "accountSettingsDescription": "Manage your preferences on the Solar Network.", "settings": "Settings", "language": "Language", "accountLanguageHint": "This language will be used for email and push notifications.", @@ -251,6 +258,7 @@ "translatorBadgeName": "Translator", "translatorBadgeDescription": "Helping translate Solar Network into different languages", "wallet": "Wallet", + "walletDescription": "Your source point wallet.", "walletCurrencyPoints": "New Solar Points", "walletCurrencyShortPoints": "NSP", "walletCurrencyGolds": "The Solar Dollars", @@ -258,6 +266,7 @@ "retry": "Retry", "creatorHubUnselectedHint": "Pick / create a publisher to get started.", "relationships": "Relationships", + "relationshipsDescription": "Friends and connections.", "addFriend": "Send a Friend Request", "addFriendShort": "Add as Friend", "addFriendHint": "Add a friend to your relationship list.", @@ -670,7 +679,9 @@ "publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.", "learnMore": "Learn More", "webArticlesStand": "Article Stand", + "webArticlesStandDescription": "Explore external sites articles.", "about": "About", + "aboutDescription": "Learn more about the Solar Network.", "somethingWentWrong": "Something went wrong", "editedAt": "Edited at {}", "addAudio": "Add audio", @@ -691,6 +702,7 @@ "sharePostPhoto": "Share Post as Photo", "wouldYouLikeToNavigateToChat": "Would You like to navigate to the chat?", "abuseReports": "Abuse Reports", + "abuseReportsDescription": "View and manage abuse reports.", "membershipCancel": "Cancel Membership", "membershipCancelConfirm": "Are you sure to cancel your membership?", "membershipCancelHint": "Are you sure to cancel your membership? You will not be charged again. Your membership will remain active until the end of the current billing period. And you will not able to resubscribe until the end of the current subscription ends.", @@ -762,6 +774,7 @@ "publisherCannotBeEmpty": "Publisher cannot be empty", "operationFailed": "Operation failed: {}", "stickerMarketplace": "Sticker Marketplace", + "stickerMarketplaceDescription": "Browse and add sticker packs from the Solar Network marketplace.", "stickerPackAdded": "Sticker pack added to your collection", "stickerPackRemoved": "Sticker pack removed from your collection", "addPack": "Add Pack", @@ -789,6 +802,7 @@ "joinedAt": "Joined at {}", "searchAccounts": "Search accounts...", "webFeeds": "Web Feeds", + "webFeedsDescription": "Browse and subscribe to web feeds from the Solar Network.", "polls": "Polls", "sharePostSlogan": "Explore more on the Solar Network", "filesListAdditional": { @@ -867,6 +881,7 @@ "contactMethodPublic": "Public", "contactMethodPrivate": "Private", "discoverRealms": "Realms", + "discoverRealmsDescription": "Discover new realms and join them.", "discoverPublishers": "Publishers", "discoverShuffledPost": "Random Posts", "projects": "Projects", @@ -910,7 +925,9 @@ "fileHash": "File Hash", "exifData": "EXIF Data", "postShuffle": "Shuffle Posts", + "postShuffleDescription": "Shuffle posts to see the posts randomly.", "leveling": "Leveling", + "levelingDescription": "See your leveling progress and history.", "levelingHistory": "Leveling History", "stellarProgram": "Stellar Program", "socialCredits": "Social Credits", @@ -1053,6 +1070,7 @@ "fileSizeExceeded": "File size exceeds the maximum limit of {}", "fileTypeNotAccepted": "File type is not accepted by this pool", "files": "Files", + "filesDescription": "Manage your files on the Solar Network Drive.", "confirmDeleteFile": "Are you sure you want to delete this file?", "deleteFile": "Delete File", "failedToDeleteFile": "Failed to delete file", @@ -1319,6 +1337,7 @@ "backToHub": "Back to Hub", "advancedFilters": "Advanced Filters", "searchPosts": "Search Posts", + "searchPostsDescription": "Search posts by title, content, or else.", "sortBy": "Sort by", "fromDate": "From Date", "toDate": "To Date", @@ -1502,58 +1521,10 @@ "recentChats": "Recent Chats", "noFeaturedPostsAvailable": "No featured posts available", "searchChatsAndPages": "Search chats and pages...", - "routeDashboard": "Dashboard", - "routeDashboardDesc": "Main dashboard", - "routeExplore": "Explore", - "routeExploreDesc": "Discover content", - "routePostSearch": "Post Search", - "routePostSearchDesc": "Search posts", - "routePostShuffle": "Post Shuffle", - "routePostShuffleDesc": "Random posts", - "routePostCategories": "Post Categories", - "routePostCategoriesDesc": "Browse categories", - "routeDiscoveryRealms": "Discovery Realms", - "routeDiscoveryRealmsDesc": "Explore realms", - "routeChat": "Chat", - "routeChatDesc": "Messages and conversations", - "routeRealms": "Realms", - "routeRealmsDesc": "Community realms", - "routeAccount": "Account", - "routeAccountDesc": "Your profile and settings", - "routeStickerMarketplace": "Sticker Marketplace", - "routeStickerMarketplaceDesc": "Browse sticker packs", - "routeWebFeeds": "Web Feeds", - "routeWebFeedsDesc": "RSS and web feeds", - "routeWallet": "Wallet", - "routeWalletDesc": "Your digital wallet", - "routeRelationships": "Relationships", - "routeRelationshipsDesc": "Friends and connections", - "routeUpdateProfile": "Update Profile", - "routeUpdateProfileDesc": "Edit your profile", - "routeLeveling": "Leveling", - "routeLevelingDesc": "Your progress and levels", - "routeAccountSettings": "Account Settings", - "routeAccountSettingsDesc": "App preferences", - "routeReports": "Reports", - "routeReportsDesc": "Your abuse reports", - "routeFiles": "Files", - "routeFilesDesc": "File manager", - "routeThought": "Thought", - "routeThoughtDesc": "AI assistant", - "routeCreatorHub": "Creator Hub", - "routeCreatorHubDesc": "Content creation tools", - "routeDeveloperHub": "Developer Hub", - "routeDeveloperHubDesc": "Developer tools", - "routeLogs": "Logs", - "routeLogsDesc": "Application logs", - "routeArticles": "Articles", - "routeArticlesDesc": "Web articles", - "routeLogin": "Login", - "routeLoginDesc": "Sign in to your account", - "routeCreateAccount": "Create Account", - "routeCreateAccountDesc": "Create a new account", - "routeSettings": "Settings", - "routeSettingsDesc": "Application settings", - "routeAbout": "About", - "routeAboutDesc": "About this app" + "dashboard": "Dashboard", + "dashboardDescription": "All your data in one place.", + "postTagsCategories": "Post Tags and Categories", + "postTagsCategoriesDescription": "Browse posts by category and tags.", + "debugLogs": "Debug Logs", + "debugLogsDescription": "View debug logs for troubleshooting." } diff --git a/lib/models/route_item.dart b/lib/models/route_item.dart index cb7b4d92..703ea4f9 100644 --- a/lib/models/route_item.dart +++ b/lib/models/route_item.dart @@ -1,5 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:material_symbols_icons/symbols.dart'; part 'route_item.freezed.dart'; @@ -9,6 +11,221 @@ sealed class RouteItem with _$RouteItem { required String name, required String path, required String description, + @Default([]) List searchableAliases, required IconData icon, }) = _RouteItem; } + +final List kAvailableRoutes = [ + RouteItem( + name: 'dashboard'.tr(), + path: '/', + description: 'dashboardDescription'.tr(), + searchableAliases: ['dashboard', 'home'], + icon: Symbols.home, + ), + RouteItem( + name: 'explore'.tr(), + path: '/explore', + description: 'exploreDescription'.tr(), + searchableAliases: ['explore', 'discover'], + icon: Symbols.explore, + ), + RouteItem( + name: 'searchPosts'.tr(), + path: '/posts/search', + description: 'searchPostsDescription'.tr(), + searchableAliases: ['search', 'posts'], + icon: Symbols.search, + ), + RouteItem( + name: 'postShuffle'.tr(), + path: '/posts/shuffle', + description: 'postShuffleDescription'.tr(), + searchableAliases: ['shuffle', 'random', 'posts'], + icon: Symbols.shuffle, + ), + RouteItem( + name: 'postTagsCategories'.tr(), + path: '/posts/categories', + description: 'postTagsCategoriesDescription'.tr(), + searchableAliases: ['tags', 'categories', 'posts'], + icon: Symbols.category, + ), + RouteItem( + name: 'discoverRealms'.tr(), + path: '/discovery/realms', + description: 'discoverRealmsDescription'.tr(), + searchableAliases: ['realms', 'groups', 'communities'], + icon: Symbols.public, + ), + RouteItem( + name: 'chat'.tr(), + path: '/chat', + description: 'chatDescription'.tr(), + searchableAliases: ['chat', 'messages', 'conversations', 'dm'], + icon: Symbols.chat, + ), + RouteItem( + name: 'realms'.tr(), + path: '/realms', + description: 'realmsDescription'.tr(), + searchableAliases: ['realms', 'groups', 'communities'], + icon: Symbols.group, + ), + RouteItem( + name: 'account'.tr(), + path: '/account', + description: 'accountDescription'.tr(), + searchableAliases: ['account', 'me', 'profile', 'user'], + icon: Symbols.person, + ), + RouteItem( + name: 'stickerMarketplace'.tr(), + path: '/stickers', + description: 'stickerMarketplaceDescription'.tr(), + searchableAliases: ['stickers', 'marketplace', 'emojis', 'emojis'], + icon: Symbols.emoji_emotions, + ), + RouteItem( + name: 'webFeeds'.tr(), + path: '/feeds', + description: 'webFeedsDescription'.tr(), + searchableAliases: ['feeds', 'web feeds', 'rss', 'news'], + icon: Symbols.feed, + ), + RouteItem( + name: 'wallet'.tr(), + path: '/account/wallet', + description: 'walletDescription'.tr(), + searchableAliases: [ + 'wallet', + 'balance', + 'money', + 'source points', + 'gold points', + 'nsp', + 'shd', + ], + icon: Symbols.account_balance_wallet, + ), + RouteItem( + name: 'relationships'.tr(), + path: '/account/relationships', + description: 'relationshipsDescription'.tr(), + searchableAliases: ['relationships', 'friends', 'block list', 'blocks'], + icon: Symbols.people, + ), + RouteItem( + name: 'updateYourProfile'.tr(), + path: '/account/me/update', + description: 'updateYourProfileDescription'.tr(), + searchableAliases: ['profile', 'update', 'edit', 'my profile'], + icon: Symbols.edit, + ), + RouteItem( + name: 'leveling'.tr(), + path: '/account/me/leveling', + description: 'levelingDescription'.tr(), + searchableAliases: [ + 'leveling', + 'level', + 'levels', + 'subscriptions', + 'social credits', + ], + icon: Symbols.trending_up, + ), + RouteItem( + name: 'accountSettings'.tr(), + path: '/account/me/settings', + description: 'accountSettingsDescription'.tr(), + searchableAliases: [ + 'settings', + 'preferences', + 'account', + 'account settings', + ], + icon: Symbols.settings, + ), + RouteItem( + name: 'abuseReports'.tr(), + path: '/safety/reports/me', + description: 'abuseReportsDescription'.tr(), + searchableAliases: ['reports', 'abuse', 'safety'], + icon: Symbols.report, + ), + RouteItem( + name: 'files'.tr(), + path: '/files', + description: 'filesDescription'.tr(), + searchableAliases: ['files', 'folders', 'storage', 'drive', 'cloud'], + icon: Symbols.folder, + ), + RouteItem( + name: 'aiThought'.tr(), + path: '/thought', + description: 'aiThoughtTitle'.tr(), + searchableAliases: ['thought', 'ai', 'ai thought'], + icon: Symbols.psychology, + ), + RouteItem( + name: 'creatorHub'.tr(), + path: '/creators', + description: 'creatorHubDescription'.tr(), + searchableAliases: ['creators', 'hub', 'creator hub', 'creators hub'], + icon: Symbols.create, + ), + RouteItem( + name: 'developerPortal'.tr(), + path: '/developers', + description: 'developerPortalDescription'.tr(), + searchableAliases: [ + 'developers', + 'dev', + 'developer', + 'developer hub', + 'developers hub', + ], + icon: Symbols.code, + ), + RouteItem( + name: 'debugLogs'.tr(), + path: '/logs', + description: 'debugLogsDescription'.tr(), + searchableAliases: ['logs', 'debug', 'debug logs'], + icon: Symbols.bug_report, + ), + RouteItem( + name: 'webArticlesStand'.tr(), + path: '/feeds/articles', + description: 'webArticlesStandDescription'.tr(), + searchableAliases: ['articles', 'stand', 'feed', 'web feed'], + icon: Symbols.article, + ), + RouteItem( + name: 'appSettings'.tr(), + path: '/settings', + description: 'appSettingsDescription'.tr(), + searchableAliases: ['settings', 'preferences', 'app', 'app settings'], + icon: Symbols.settings, + ), + RouteItem( + name: 'about'.tr(), + path: '/about', + description: 'about'.tr(), + searchableAliases: ['about', 'info'], + icon: Symbols.info, + ), +]; + +@freezed +sealed class SpecialAction with _$SpecialAction { + const factory SpecialAction({ + required String name, + required String description, + required IconData icon, + required VoidCallback action, + @Default([]) List searchableAliases, + }) = _SpecialAction; +} diff --git a/lib/models/route_item.freezed.dart b/lib/models/route_item.freezed.dart index 9b07637e..6a2ad1dd 100644 --- a/lib/models/route_item.freezed.dart +++ b/lib/models/route_item.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$RouteItem { - String get name; String get path; String get description; IconData get icon; + String get name; String get path; String get description; List get searchableAliases; IconData get icon; /// Create a copy of RouteItem /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $RouteItemCopyWith get copyWith => _$RouteItemCopyWithImpl @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.searchableAliases, searchableAliases)&&(identical(other.icon, icon) || other.icon == icon)); } @override -int get hashCode => Object.hash(runtimeType,name,path,description,icon); +int get hashCode => Object.hash(runtimeType,name,path,description,const DeepCollectionEquality().hash(searchableAliases),icon); @override String toString() { - return 'RouteItem(name: $name, path: $path, description: $description, icon: $icon)'; + return 'RouteItem(name: $name, path: $path, description: $description, searchableAliases: $searchableAliases, icon: $icon)'; } @@ -45,7 +45,7 @@ abstract mixin class $RouteItemCopyWith<$Res> { factory $RouteItemCopyWith(RouteItem value, $Res Function(RouteItem) _then) = _$RouteItemCopyWithImpl; @useResult $Res call({ - String name, String path, String description, IconData icon + String name, String path, String description, List searchableAliases, IconData icon }); @@ -62,12 +62,13 @@ class _$RouteItemCopyWithImpl<$Res> /// Create a copy of RouteItem /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? path = null,Object? description = null,Object? icon = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? path = null,Object? description = null,Object? searchableAliases = null,Object? icon = null,}) { return _then(_self.copyWith( name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable -as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as String,searchableAliases: null == searchableAliases ? _self.searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable +as List,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable as IconData, )); } @@ -150,10 +151,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String name, String path, String description, IconData icon)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String name, String path, String description, List searchableAliases, IconData icon)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _RouteItem() when $default != null: -return $default(_that.name,_that.path,_that.description,_that.icon);case _: +return $default(_that.name,_that.path,_that.description,_that.searchableAliases,_that.icon);case _: return orElse(); } @@ -171,10 +172,10 @@ return $default(_that.name,_that.path,_that.description,_that.icon);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String name, String path, String description, IconData icon) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String name, String path, String description, List searchableAliases, IconData icon) $default,) {final _that = this; switch (_that) { case _RouteItem(): -return $default(_that.name,_that.path,_that.description,_that.icon);} +return $default(_that.name,_that.path,_that.description,_that.searchableAliases,_that.icon);} } /// A variant of `when` that fallback to returning `null` /// @@ -188,10 +189,10 @@ return $default(_that.name,_that.path,_that.description,_that.icon);} /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String name, String path, String description, IconData icon)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String name, String path, String description, List searchableAliases, IconData icon)? $default,) {final _that = this; switch (_that) { case _RouteItem() when $default != null: -return $default(_that.name,_that.path,_that.description,_that.icon);case _: +return $default(_that.name,_that.path,_that.description,_that.searchableAliases,_that.icon);case _: return null; } @@ -203,12 +204,19 @@ return $default(_that.name,_that.path,_that.description,_that.icon);case _: class _RouteItem implements RouteItem { - const _RouteItem({required this.name, required this.path, required this.description, required this.icon}); + const _RouteItem({required this.name, required this.path, required this.description, final List searchableAliases = const [], required this.icon}): _searchableAliases = searchableAliases; @override final String name; @override final String path; @override final String description; + final List _searchableAliases; +@override@JsonKey() List get searchableAliases { + if (_searchableAliases is EqualUnmodifiableListView) return _searchableAliases; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_searchableAliases); +} + @override final IconData icon; /// Create a copy of RouteItem @@ -221,16 +229,16 @@ _$RouteItemCopyWith<_RouteItem> get copyWith => __$RouteItemCopyWithImpl<_RouteI @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._searchableAliases, _searchableAliases)&&(identical(other.icon, icon) || other.icon == icon)); } @override -int get hashCode => Object.hash(runtimeType,name,path,description,icon); +int get hashCode => Object.hash(runtimeType,name,path,description,const DeepCollectionEquality().hash(_searchableAliases),icon); @override String toString() { - return 'RouteItem(name: $name, path: $path, description: $description, icon: $icon)'; + return 'RouteItem(name: $name, path: $path, description: $description, searchableAliases: $searchableAliases, icon: $icon)'; } @@ -241,7 +249,7 @@ abstract mixin class _$RouteItemCopyWith<$Res> implements $RouteItemCopyWith<$Re factory _$RouteItemCopyWith(_RouteItem value, $Res Function(_RouteItem) _then) = __$RouteItemCopyWithImpl; @override @useResult $Res call({ - String name, String path, String description, IconData icon + String name, String path, String description, List searchableAliases, IconData icon }); @@ -258,17 +266,287 @@ class __$RouteItemCopyWithImpl<$Res> /// Create a copy of RouteItem /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? path = null,Object? description = null,Object? icon = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? path = null,Object? description = null,Object? searchableAliases = null,Object? icon = null,}) { return _then(_RouteItem( name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable -as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as String,searchableAliases: null == searchableAliases ? _self._searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable +as List,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable as IconData, )); } +} + +/// @nodoc +mixin _$SpecialAction { + + String get name; String get description; IconData get icon; VoidCallback get action; List get searchableAliases; +/// Create a copy of SpecialAction +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SpecialActionCopyWith get copyWith => _$SpecialActionCopyWithImpl(this as SpecialAction, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SpecialAction&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.action, action) || other.action == action)&&const DeepCollectionEquality().equals(other.searchableAliases, searchableAliases)); +} + + +@override +int get hashCode => Object.hash(runtimeType,name,description,icon,action,const DeepCollectionEquality().hash(searchableAliases)); + +@override +String toString() { + return 'SpecialAction(name: $name, description: $description, icon: $icon, action: $action, searchableAliases: $searchableAliases)'; +} + + +} + +/// @nodoc +abstract mixin class $SpecialActionCopyWith<$Res> { + factory $SpecialActionCopyWith(SpecialAction value, $Res Function(SpecialAction) _then) = _$SpecialActionCopyWithImpl; +@useResult +$Res call({ + String name, String description, IconData icon, VoidCallback action, List searchableAliases +}); + + + + +} +/// @nodoc +class _$SpecialActionCopyWithImpl<$Res> + implements $SpecialActionCopyWith<$Res> { + _$SpecialActionCopyWithImpl(this._self, this._then); + + final SpecialAction _self; + final $Res Function(SpecialAction) _then; + +/// Create a copy of SpecialAction +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? description = null,Object? icon = null,Object? action = null,Object? searchableAliases = null,}) { + return _then(_self.copyWith( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as IconData,action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable +as VoidCallback,searchableAliases: null == searchableAliases ? _self.searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SpecialAction]. +extension SpecialActionPatterns on SpecialAction { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SpecialAction value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SpecialAction() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SpecialAction value) $default,){ +final _that = this; +switch (_that) { +case _SpecialAction(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SpecialAction value)? $default,){ +final _that = this; +switch (_that) { +case _SpecialAction() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String name, String description, IconData icon, VoidCallback action, List searchableAliases)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SpecialAction() when $default != null: +return $default(_that.name,_that.description,_that.icon,_that.action,_that.searchableAliases);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String name, String description, IconData icon, VoidCallback action, List searchableAliases) $default,) {final _that = this; +switch (_that) { +case _SpecialAction(): +return $default(_that.name,_that.description,_that.icon,_that.action,_that.searchableAliases);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String name, String description, IconData icon, VoidCallback action, List searchableAliases)? $default,) {final _that = this; +switch (_that) { +case _SpecialAction() when $default != null: +return $default(_that.name,_that.description,_that.icon,_that.action,_that.searchableAliases);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _SpecialAction implements SpecialAction { + const _SpecialAction({required this.name, required this.description, required this.icon, required this.action, final List searchableAliases = const []}): _searchableAliases = searchableAliases; + + +@override final String name; +@override final String description; +@override final IconData icon; +@override final VoidCallback action; + final List _searchableAliases; +@override@JsonKey() List get searchableAliases { + if (_searchableAliases is EqualUnmodifiableListView) return _searchableAliases; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_searchableAliases); +} + + +/// Create a copy of SpecialAction +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SpecialActionCopyWith<_SpecialAction> get copyWith => __$SpecialActionCopyWithImpl<_SpecialAction>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SpecialAction&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.action, action) || other.action == action)&&const DeepCollectionEquality().equals(other._searchableAliases, _searchableAliases)); +} + + +@override +int get hashCode => Object.hash(runtimeType,name,description,icon,action,const DeepCollectionEquality().hash(_searchableAliases)); + +@override +String toString() { + return 'SpecialAction(name: $name, description: $description, icon: $icon, action: $action, searchableAliases: $searchableAliases)'; +} + + +} + +/// @nodoc +abstract mixin class _$SpecialActionCopyWith<$Res> implements $SpecialActionCopyWith<$Res> { + factory _$SpecialActionCopyWith(_SpecialAction value, $Res Function(_SpecialAction) _then) = __$SpecialActionCopyWithImpl; +@override @useResult +$Res call({ + String name, String description, IconData icon, VoidCallback action, List searchableAliases +}); + + + + +} +/// @nodoc +class __$SpecialActionCopyWithImpl<$Res> + implements _$SpecialActionCopyWith<$Res> { + __$SpecialActionCopyWithImpl(this._self, this._then); + + final _SpecialAction _self; + final $Res Function(_SpecialAction) _then; + +/// Create a copy of SpecialAction +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? description = null,Object? icon = null,Object? action = null,Object? searchableAliases = null,}) { + return _then(_SpecialAction( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as IconData,action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable +as VoidCallback,searchableAliases: null == searchableAliases ? _self._searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + } // dart format on diff --git a/lib/screens/dashboard/dash.dart b/lib/screens/dashboard/dash.dart index 8dc636a3..9cd42f65 100644 --- a/lib/screens/dashboard/dash.dart +++ b/lib/screens/dashboard/dash.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -61,20 +62,37 @@ class DashboardGrid extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ // Clock card spans full width - ClockCard().padding(horizontal: isWide ? 24 : 16), + if (isWide) + ClockCard().padding(horizontal: 24) + else + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Gap(8), + Expanded(child: ClockCard(compact: true)), + IconButton( + onPressed: () { + eventBus.fire(CommandPaletteTriggerEvent()); + }, + icon: const Icon(Symbols.search), + tooltip: 'searchAnything'.tr(), + ), + ], + ).padding(horizontal: 24), // Row with two cards side by side - Padding( - padding: EdgeInsets.symmetric(horizontal: isWide ? 24 : 16), - child: SearchBar( - hintText: 'Search Anything...', - constraints: const BoxConstraints(minHeight: 56), - leading: const Icon(Symbols.search).padding(horizontal: 24), - readOnly: true, - onTap: () { - eventBus.fire(CommandPaletteTriggerEvent()); - }, + if (isWide) + Padding( + padding: EdgeInsets.symmetric(horizontal: isWide ? 24 : 16), + child: SearchBar( + hintText: 'searchAnything'.tr(), + constraints: const BoxConstraints(minHeight: 56), + leading: const Icon(Symbols.search).padding(horizontal: 24), + readOnly: true, + onTap: () { + eventBus.fire(CommandPaletteTriggerEvent()); + }, + ), ), - ), if (userInfo.value != null) Expanded( child: @@ -190,7 +208,8 @@ class _DashboardGridNarrow extends HookConsumerWidget { } class ClockCard extends HookConsumerWidget { - const ClockCard({super.key}); + final bool compact; + const ClockCard({super.key, this.compact = false}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -198,6 +217,12 @@ class ClockCard extends HookConsumerWidget { final timer = useRef(null); final nextNotableDay = ref.watch(nextNotableDayProvider); + // Determine icon based on time of day + final int hour = time.value.hour; + final IconData timeIcon = (hour >= 6 && hour < 18) + ? Symbols.sunny_rounded + : Symbols.dark_mode_rounded; + useEffect(() { timer.value = Timer.periodic(const Duration(seconds: 1), (_) { time.value = DateTime.now(); @@ -213,14 +238,16 @@ class ClockCard extends HookConsumerWidget { borderRadius: BorderRadius.all(Radius.circular(12)), ), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: compact + ? EdgeInsets.zero + : const EdgeInsets.symmetric(horizontal: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( - Symbols.schedule, + timeIcon, size: 32, color: Theme.of(context).colorScheme.primary, ), @@ -321,9 +348,11 @@ class FeaturedPostCard extends HookConsumerWidget { error: (error, stack) => Center(child: Text('Error: $error')), data: (posts) { if (posts.isEmpty) { - return const Padding( + return Padding( padding: EdgeInsets.all(16), - child: Center(child: Text('No featured posts available')), + child: Center( + child: Text('noFeaturedPostsAvailable').tr(), + ), ); } return PageView.builder( @@ -384,7 +413,7 @@ class NotificationsCard extends HookConsumerWidget { const SizedBox(width: 8), Expanded( child: Text( - 'Notifications', + 'notifications'.tr(), style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), @@ -397,7 +426,7 @@ class NotificationsCard extends HookConsumerWidget { error: (error, stack) => Center(child: Text('Error: $error')), data: (notificationList) { if (notificationList.isEmpty) { - return const Center(child: Text('No notifications yet')); + return Center(child: Text('noNotificationsYet').tr()); } // Get the most recent notification (first in the list) final recentNotification = notificationList.first; @@ -406,7 +435,7 @@ class NotificationsCard extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Most Recent', + 'mostRecent'.tr(), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -423,7 +452,7 @@ class NotificationsCard extends HookConsumerWidget { }, ), Text( - 'Tap to view all notifications', + 'tapToViewAllNotifications'.tr(), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -460,7 +489,7 @@ class ChatListCard extends HookConsumerWidget { const SizedBox(width: 8), Expanded( child: Text( - 'Recent Chats', + 'recentChats'.tr(), style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), diff --git a/lib/widgets/cmp/pattle.dart b/lib/widgets/cmp/pattle.dart index d5678a65..b134af2f 100644 --- a/lib/widgets/cmp/pattle.dart +++ b/lib/widgets/cmp/pattle.dart @@ -20,190 +20,11 @@ import 'package:relative_time/relative_time.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:island/services/event_bus.dart'; -class SpecialAction { - final String name; - final String description; - final IconData icon; - final VoidCallback action; - - const SpecialAction({ - required this.name, - required this.description, - required this.icon, - required this.action, - }); -} - class CommandPattleWidget extends HookConsumerWidget { final VoidCallback onDismiss; const CommandPattleWidget({super.key, required this.onDismiss}); - static final List _availableRoutes = [ - RouteItem( - name: 'Dashboard', - path: '/', - description: 'Main dashboard', - icon: Symbols.home, - ), - RouteItem( - name: 'Explore', - path: '/explore', - description: 'Discover content', - icon: Symbols.explore, - ), - RouteItem( - name: 'Post Search', - path: '/posts/search', - description: 'Search posts', - icon: Symbols.search, - ), - RouteItem( - name: 'Post Shuffle', - path: '/posts/shuffle', - description: 'Random posts', - icon: Symbols.shuffle, - ), - RouteItem( - name: 'Post Categories', - path: '/posts/categories', - description: 'Browse categories', - icon: Symbols.category, - ), - RouteItem( - name: 'Discovery Realms', - path: '/discovery/realms', - description: 'Explore realms', - icon: Symbols.public, - ), - RouteItem( - name: 'Chat', - path: '/chat', - description: 'Messages and conversations', - icon: Symbols.chat, - ), - RouteItem( - name: 'Realms', - path: '/realms', - description: 'Community realms', - icon: Symbols.group, - ), - RouteItem( - name: 'Account', - path: '/account', - description: 'Your profile and settings', - icon: Symbols.person, - ), - RouteItem( - name: 'Sticker Marketplace', - path: '/stickers', - description: 'Browse sticker packs', - icon: Symbols.emoji_emotions, - ), - RouteItem( - name: 'Web Feeds', - path: '/feeds', - description: 'RSS and web feeds', - icon: Symbols.feed, - ), - RouteItem( - name: 'Wallet', - path: '/account/wallet', - description: 'Your digital wallet', - icon: Symbols.account_balance_wallet, - ), - RouteItem( - name: 'Relationships', - path: '/account/relationships', - description: 'Friends and connections', - icon: Symbols.people, - ), - RouteItem( - name: 'Update Profile', - path: '/account/me/update', - description: 'Edit your profile', - icon: Symbols.edit, - ), - RouteItem( - name: 'Leveling', - path: '/account/me/leveling', - description: 'Your progress and levels', - icon: Symbols.trending_up, - ), - RouteItem( - name: 'Account Settings', - path: '/account/me/settings', - description: 'App preferences', - icon: Symbols.settings, - ), - RouteItem( - name: 'Reports', - path: '/safety/reports/me', - description: 'Your abuse reports', - icon: Symbols.report, - ), - RouteItem( - name: 'Files', - path: '/files', - description: 'File manager', - icon: Symbols.folder, - ), - RouteItem( - name: 'Thought', - path: '/thought', - description: 'AI assistant', - icon: Symbols.psychology, - ), - RouteItem( - name: 'Creator Hub', - path: '/creators', - description: 'Content creation tools', - icon: Symbols.create, - ), - RouteItem( - name: 'Developer Hub', - path: '/developers', - description: 'Developer tools', - icon: Symbols.code, - ), - RouteItem( - name: 'Logs', - path: '/logs', - description: 'Application logs', - icon: Symbols.bug_report, - ), - RouteItem( - name: 'Articles', - path: '/feeds/articles', - description: 'Web articles', - icon: Symbols.article, - ), - RouteItem( - name: 'Login', - path: '/auth/login', - description: 'Sign in to your account', - icon: Symbols.login, - ), - RouteItem( - name: 'Create Account', - path: '/auth/create-account', - description: 'Create a new account', - icon: Symbols.person_add, - ), - RouteItem( - name: 'Settings', - path: '/settings', - description: 'Application settings', - icon: Symbols.settings, - ), - RouteItem( - name: 'About', - path: '/about', - description: 'About this app', - icon: Symbols.info, - ), - ]; - static List _getSpecialActions(BuildContext context) { return [ SpecialAction( @@ -296,11 +117,14 @@ class CommandPattleWidget extends HookConsumerWidget { final filteredRoutes = searchQuery.value.isEmpty ? [] - : _availableRoutes + : kAvailableRoutes .where((route) { final query = searchQuery.value.toLowerCase(); return route.name.toLowerCase().contains(query) || - route.description.toLowerCase().contains(query); + route.description.toLowerCase().contains(query) || + route.searchableAliases.any( + (e) => e.toLowerCase().contains(query), + ); }) .take(5) // Limit to 5 results .toList(); @@ -311,7 +135,10 @@ class CommandPattleWidget extends HookConsumerWidget { .where((action) { final query = searchQuery.value.toLowerCase(); return action.name.toLowerCase().contains(query) || - action.description.toLowerCase().contains(query); + action.description.toLowerCase().contains(query) || + action.searchableAliases.any( + (e) => e.toLowerCase().contains(query), + ); }) .take(5) // Limit to 5 results .toList(); @@ -452,7 +279,7 @@ class CommandPattleWidget extends HookConsumerWidget { SearchBar( controller: textController, focusNode: focusNode, - hintText: 'Search chats and pages...', + hintText: 'searchChatsAndPages'.tr(), leading: CircleAvatar( child: const Icon(Symbols.keyboard_command_key), ).padding(horizontal: 8), diff --git a/macos/Podfile.lock b/macos/Podfile.lock index dad0ecdf..5846f7ea 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -173,6 +173,8 @@ PODS: - hotkey_manager_macos (0.0.1): - FlutterMacOS - HotKey + - in_app_review (2.0.0): + - FlutterMacOS - irondash_engine_context (0.0.1): - FlutterMacOS - KeychainAccess (4.2.2) @@ -279,6 +281,7 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`) - hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`) + - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) - irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`) - livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`) - local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`) @@ -367,6 +370,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/gal/darwin hotkey_manager_macos: :path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos + in_app_review: + :path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos irondash_engine_context: :path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos livekit_client: @@ -447,6 +452,7 @@ SPEC CHECKSUMS: GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe + in_app_review: 66e7680752b632d83f4f0e88b34d52ed303fbff4 irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 livekit_client: 3df5a1787d64010ca56c4002959d9e47c03ba3fb