🌐 Localized the dashboard and the command pattle

This commit is contained in:
2025-12-21 12:37:27 +08:00
parent b0085c2ab0
commit 7a56e7882e
6 changed files with 607 additions and 279 deletions

View File

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

View File

@@ -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<String> searchableAliases,
required IconData icon,
}) = _RouteItem;
}
final List<RouteItem> 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<String> searchableAliases,
}) = _SpecialAction;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(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<String> 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<RouteItem> get copyWith => _$RouteItemCopyWithImpl<RouteItem>
@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<String> 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<String>,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 extends Object?>(TResult Function( String name, String path, String description, IconData icon)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String path, String description, List<String> 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 extends Object?>(TResult Function( String name, String path, String description, IconData icon) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String path, String description, List<String> 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 extends Object?>(TResult? Function( String name, String path, String description, IconData icon)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String path, String description, List<String> 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<String> searchableAliases = const [], required this.icon}): _searchableAliases = searchableAliases;
@override final String name;
@override final String path;
@override final String description;
final List<String> _searchableAliases;
@override@JsonKey() List<String> 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<String> 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<String>,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<String> 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<SpecialAction> get copyWith => _$SpecialActionCopyWithImpl<SpecialAction>(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<String> 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<String>,
));
}
}
/// 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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(TResult Function( String name, String description, IconData icon, VoidCallback action, List<String> 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 extends Object?>(TResult Function( String name, String description, IconData icon, VoidCallback action, List<String> 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 extends Object?>(TResult? Function( String name, String description, IconData icon, VoidCallback action, List<String> 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<String> searchableAliases = const []}): _searchableAliases = searchableAliases;
@override final String name;
@override final String description;
@override final IconData icon;
@override final VoidCallback action;
final List<String> _searchableAliases;
@override@JsonKey() List<String> 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<String> 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<String>,
));
}
}
// dart format on

View File

@@ -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,12 +62,29 @@ 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
if (isWide)
Padding(
padding: EdgeInsets.symmetric(horizontal: isWide ? 24 : 16),
child: SearchBar(
hintText: 'Search Anything...',
hintText: 'searchAnything'.tr(),
constraints: const BoxConstraints(minHeight: 56),
leading: const Icon(Symbols.search).padding(horizontal: 24),
readOnly: true,
@@ -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<Timer?>(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,
),

View File

@@ -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<RouteItem> _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<SpecialAction> _getSpecialActions(BuildContext context) {
return [
SpecialAction(
@@ -296,11 +117,14 @@ class CommandPattleWidget extends HookConsumerWidget {
final filteredRoutes = searchQuery.value.isEmpty
? <RouteItem>[]
: _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),

View File

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