Compare commits

...

15 Commits

Author SHA1 Message Date
LittleSheep
c9b71701c8 🚀 Launch 3.2.0+129 2025-08-26 01:33:57 +08:00
LittleSheep
28e98488f1 🌐 Localize new stuff 2025-08-26 01:24:58 +08:00
LittleSheep
b4d476613e 🐛 Optimzation and bug fixes 2025-08-26 01:18:54 +08:00
LittleSheep
b48a1aac44 Search messages!
♻️ Optimize messages loading, syncing
2025-08-26 01:05:30 +08:00
LittleSheep
596d212593 🐛 Fix account name localization 2025-08-26 00:17:34 +08:00
LittleSheep
54f290327e Copyable file ID 2025-08-26 00:08:50 +08:00
LittleSheep
16f248ceab 💄 Optimize file saving 2025-08-26 00:03:43 +08:00
LittleSheep
856d811187 🐛 Fix notification tap in system wide 2025-08-25 23:14:59 +08:00
LittleSheep
d07b194c04 🐛 Fixes 2025-08-25 20:44:35 +08:00
LittleSheep
2554b58be6 Show automated account in pfc 2025-08-25 20:31:43 +08:00
LittleSheep
a627b5838e 👔 Disable pin reply 2025-08-25 19:57:30 +08:00
LittleSheep
c479a9f381 Show social credits 2025-08-25 19:56:34 +08:00
LittleSheep
02057e663b Real previewing chat 2025-08-25 19:36:48 +08:00
LittleSheep
6501594100 Event dairy 2025-08-25 18:31:57 +08:00
LittleSheep
c6599edc3d 💄 Serval changes to optimize UX 2025-08-25 18:03:50 +08:00
38 changed files with 1213 additions and 306 deletions

View File

@@ -884,6 +884,7 @@
"stellarProgram": "Stellar Program", "stellarProgram": "Stellar Program",
"socialCredits": "Social Credits", "socialCredits": "Social Credits",
"credits": "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.", "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", "socialCreditsLevelPoor": "Poor",
"socialCreditsLevelNormal": "Normal", "socialCreditsLevelNormal": "Normal",
@@ -941,5 +942,23 @@
"pinPostReplyDisabledHint": "This post is not a reply", "pinPostReplyDisabledHint": "This post is not a reply",
"pin": "Pin", "pin": "Pin",
"unpinPostHint": "Are you sure you want to unpin this post?", "unpinPostHint": "Are you sure you want to unpin this post?",
"all": "All" "all": "All",
} "statusPresent": "Present",
"accountAutomated": "Automated",
"chatBreakClearButton": "Clear",
"chatBreak5m": "5m",
"chatBreak10m": "10m",
"chatBreak15m": "15m",
"chatBreak30m": "30m",
"chatBreakCustomMinutes": "Custom (minutes)",
"chatBreakEnterMinutes": "Enter 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"
}

View File

@@ -855,5 +855,7 @@
"newSecretGenerated": "已生成新密钥", "newSecretGenerated": "已生成新密钥",
"copySecretHint": "请复制此密钥并将其存放在安全的地方。您将无法再次看到它。", "copySecretHint": "请复制此密钥并将其存放在安全的地方。您将无法再次看到它。",
"expiresIn": "过期时间(秒)", "expiresIn": "过期时间(秒)",
"isOidc": "OIDC 兼容" "isOidc": "OIDC 兼容",
"statusPresent": "至今",
"accountAutomated": "机器人"
} }

View File

@@ -40,6 +40,8 @@ PODS:
- file_picker (0.0.1): - file_picker (0.0.1):
- DKImagePickerController/PhotoGallery - DKImagePickerController/PhotoGallery
- Flutter - Flutter
- file_saver (0.0.1):
- Flutter
- Firebase/CoreOnly (12.0.0): - Firebase/CoreOnly (12.0.0):
- FirebaseCore (~> 12.0.0) - FirebaseCore (~> 12.0.0)
- Firebase/Crashlytics (12.0.0): - Firebase/Crashlytics (12.0.0):
@@ -303,6 +305,7 @@ DEPENDENCIES:
- croppy (from `.symlinks/plugins/croppy/ios`) - croppy (from `.symlinks/plugins/croppy/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- file_saver (from `.symlinks/plugins/file_saver/ios`)
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
@@ -381,6 +384,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
file_picker: file_picker:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
file_saver:
:path: ".symlinks/plugins/file_saver/ios"
firebase_analytics: firebase_analytics:
:path: ".symlinks/plugins/firebase_analytics/ios" :path: ".symlinks/plugins/firebase_analytics/ios"
firebase_core: firebase_core:
@@ -464,6 +469,7 @@ SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d
firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4 firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4

View File

@@ -68,6 +68,34 @@ class AppDatabase extends _$AppDatabase {
return (delete(chatMessages)..where((m) => m.id.equals(id))).go(); return (delete(chatMessages)..where((m) => m.id.equals(id))).go();
} }
Future<int> getTotalMessagesForRoom(String roomId) {
return (select(chatMessages)..where((m) => m.roomId.equals(roomId))).get().then((list) => list.length);
}
Future<List<LocalChatMessage>> searchMessages(
String roomId,
String query,
) async {
var selectStatement = select(chatMessages)
..where((m) => m.roomId.equals(roomId));
if (query.isNotEmpty) {
selectStatement =
selectStatement
..where((m) => m.content.like('%${query.toLowerCase()}%'));
}
final messages =
await (selectStatement
..orderBy([(m) => OrderingTerm.desc(m.createdAt)]))
.get();
return messages.map((msg) => companionToMessage(msg)).toList();
}
// Convert between Drift and model objects // Convert between Drift and model objects
ChatMessagesCompanion messageToCompanion(LocalChatMessage message) { ChatMessagesCompanion messageToCompanion(LocalChatMessage message) {
return ChatMessagesCompanion( return ChatMessagesCompanion(

View File

@@ -8,7 +8,7 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -169,12 +169,12 @@ class IslandApp extends HookConsumerWidget {
final theme = ref.watch(themeProvider); final theme = ref.watch(themeProvider);
void handleMessage(RemoteMessage notification) { void handleMessage(RemoteMessage notification) {
if (notification.data['action_uri'] != null) { if (notification.data['meta']?['action_uri'] != null) {
var uri = notification.data['action_uri'] as String; var uri = notification.data['meta']['action_uri'] as String;
if (uri.startsWith('/')) { if (uri.startsWith('/')) {
// In-app routes // In-app routes
final router = ref.read(routerProvider); final router = ref.read(routerProvider);
router.go(notification.data['action_uri']); router.push(notification.data['meta']['action_uri']);
} else { } else {
// External links // External links
launchUrlString(uri); launchUrlString(uri);
@@ -186,27 +186,6 @@ class IslandApp extends HookConsumerWidget {
if (!kIsWeb && Platform.isLinux) { if (!kIsWeb && Platform.isLinux) {
return null; return null;
} }
const channel = MethodChannel('dev.solsynth.solian/notifications');
Future<void> handleInitialLink() async {
final String? link = await channel.invokeMethod('initialLink');
if (link != null) {
final router = ref.read(routerProvider);
router.go(link);
}
}
if (!kIsWeb && Platform.isAndroid) {
handleInitialLink();
}
channel.setMethodCallHandler((call) async {
if (call.method == 'newLink') {
final String link = call.arguments;
final router = ref.read(routerProvider);
router.go(link);
}
});
// When the app is opened from a terminated state. // When the app is opened from a terminated state.
FirebaseMessaging.instance.getInitialMessage().then((message) { FirebaseMessaging.instance.getInitialMessage().then((message) {

View File

@@ -71,6 +71,8 @@ sealed class SnAccountProfile with _$SnAccountProfile {
SnAccountBadge? activeBadge, SnAccountBadge? activeBadge,
required int experience, required int experience,
required int level, required int level,
@Default(100) double socialCredits,
@Default(0) int socialCreditsLevel,
required double levelingProgress, required double levelingProgress,
required SnCloudFile? picture, required SnCloudFile? picture,
required SnCloudFile? background, required SnCloudFile? background,

View File

@@ -613,7 +613,7 @@ as String,
/// @nodoc /// @nodoc
mixin _$SnAccountProfile { mixin _$SnAccountProfile {
String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday;@ProfileLinkConverter() List<ProfileLink> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday;@ProfileLinkConverter() List<ProfileLink> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get socialCredits; int get socialCreditsLevel; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAccountProfile /// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -626,16 +626,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other.links, links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other.links, links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.socialCredits, socialCredits) || other.socialCredits == socialCredits)&&(identical(other.socialCreditsLevel, socialCreditsLevel) || other.socialCreditsLevel == socialCreditsLevel)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(links),lastSeenAt,activeBadge,experience,level,socialCredits,socialCreditsLevel,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
@override @override
String toString() { String toString() {
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, socialCredits: $socialCredits, socialCreditsLevel: $socialCreditsLevel, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@@ -646,7 +646,7 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> {
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl; factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
@@ -663,7 +663,7 @@ class _$SnAccountProfileCopyWithImpl<$Res>
/// Create a copy of SnAccountProfile /// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? socialCredits = null,Object? socialCreditsLevel = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
@@ -680,6 +680,8 @@ as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : last
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
as int,socialCredits: null == socialCredits ? _self.socialCredits : socialCredits // ignore: cast_nullable_to_non_nullable
as double,socialCreditsLevel: null == socialCreditsLevel ? _self.socialCreditsLevel : socialCreditsLevel // ignore: cast_nullable_to_non_nullable
as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
@@ -817,10 +819,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _SnAccountProfile() when $default != null: case _SnAccountProfile() when $default != null:
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.socialCredits,_that.socialCreditsLevel,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse(); return orElse();
} }
@@ -838,10 +840,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnAccountProfile(): case _SnAccountProfile():
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);} return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.socialCredits,_that.socialCreditsLevel,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///
@@ -855,10 +857,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnAccountProfile() when $default != null: case _SnAccountProfile() when $default != null:
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.socialCredits,_that.socialCreditsLevel,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null; return null;
} }
@@ -870,7 +872,7 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
@JsonSerializable() @JsonSerializable()
class _SnAccountProfile implements SnAccountProfile { class _SnAccountProfile implements SnAccountProfile {
const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, @ProfileLinkConverter() final List<ProfileLink> links = const [], this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links; const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, @ProfileLinkConverter() final List<ProfileLink> links = const [], this.lastSeenAt, this.activeBadge, required this.experience, required this.level, this.socialCredits = 100, this.socialCreditsLevel = 0, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links;
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json); factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
@override final String id; @override final String id;
@@ -894,6 +896,8 @@ class _SnAccountProfile implements SnAccountProfile {
@override final SnAccountBadge? activeBadge; @override final SnAccountBadge? activeBadge;
@override final int experience; @override final int experience;
@override final int level; @override final int level;
@override@JsonKey() final double socialCredits;
@override@JsonKey() final int socialCreditsLevel;
@override final double levelingProgress; @override final double levelingProgress;
@override final SnCloudFile? picture; @override final SnCloudFile? picture;
@override final SnCloudFile? background; @override final SnCloudFile? background;
@@ -915,16 +919,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other._links, _links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other._links, _links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.socialCredits, socialCredits) || other.socialCredits == socialCredits)&&(identical(other.socialCreditsLevel, socialCreditsLevel) || other.socialCreditsLevel == socialCreditsLevel)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(_links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(_links),lastSeenAt,activeBadge,experience,level,socialCredits,socialCreditsLevel,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
@override @override
String toString() { String toString() {
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, socialCredits: $socialCredits, socialCreditsLevel: $socialCreditsLevel, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@@ -935,7 +939,7 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl; factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
@@ -952,7 +956,7 @@ class __$SnAccountProfileCopyWithImpl<$Res>
/// Create a copy of SnAccountProfile /// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? socialCredits = null,Object? socialCreditsLevel = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAccountProfile( return _then(_SnAccountProfile(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
@@ -969,6 +973,8 @@ as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : last
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
as int,socialCredits: null == socialCredits ? _self.socialCredits : socialCredits // ignore: cast_nullable_to_non_nullable
as double,socialCreditsLevel: null == socialCreditsLevel ? _self.socialCreditsLevel : socialCreditsLevel // ignore: cast_nullable_to_non_nullable
as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable

View File

@@ -86,6 +86,8 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
), ),
experience: (json['experience'] as num).toInt(), experience: (json['experience'] as num).toInt(),
level: (json['level'] as num).toInt(), level: (json['level'] as num).toInt(),
socialCredits: (json['social_credits'] as num?)?.toDouble() ?? 100,
socialCreditsLevel: (json['social_credits_level'] as num?)?.toInt() ?? 0,
levelingProgress: (json['leveling_progress'] as num).toDouble(), levelingProgress: (json['leveling_progress'] as num).toDouble(),
picture: picture:
json['picture'] == null json['picture'] == null
@@ -128,6 +130,8 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
'active_badge': instance.activeBadge?.toJson(), 'active_badge': instance.activeBadge?.toJson(),
'experience': instance.experience, 'experience': instance.experience,
'level': instance.level, 'level': instance.level,
'social_credits': instance.socialCredits,
'social_credits_level': instance.socialCreditsLevel,
'leveling_progress': instance.levelingProgress, 'leveling_progress': instance.levelingProgress,
'picture': instance.picture?.toJson(), 'picture': instance.picture?.toJson(),
'background': instance.background?.toJson(), 'background': instance.background?.toJson(),

View File

@@ -54,7 +54,7 @@ sealed class SnEventCalendarEntry with _$SnEventCalendarEntry {
const factory SnEventCalendarEntry({ const factory SnEventCalendarEntry({
required DateTime date, required DateTime date,
required SnCheckInResult? checkInResult, required SnCheckInResult? checkInResult,
required List<dynamic> statuses, required List<SnAccountStatus> statuses,
}) = _SnEventCalendarEntry; }) = _SnEventCalendarEntry;
factory SnEventCalendarEntry.fromJson(Map<String, dynamic> json) => factory SnEventCalendarEntry.fromJson(Map<String, dynamic> json) =>

View File

@@ -861,7 +861,7 @@ as String,
/// @nodoc /// @nodoc
mixin _$SnEventCalendarEntry { mixin _$SnEventCalendarEntry {
DateTime get date; SnCheckInResult? get checkInResult; List<dynamic> get statuses; DateTime get date; SnCheckInResult? get checkInResult; List<SnAccountStatus> get statuses;
/// Create a copy of SnEventCalendarEntry /// Create a copy of SnEventCalendarEntry
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -894,7 +894,7 @@ abstract mixin class $SnEventCalendarEntryCopyWith<$Res> {
factory $SnEventCalendarEntryCopyWith(SnEventCalendarEntry value, $Res Function(SnEventCalendarEntry) _then) = _$SnEventCalendarEntryCopyWithImpl; factory $SnEventCalendarEntryCopyWith(SnEventCalendarEntry value, $Res Function(SnEventCalendarEntry) _then) = _$SnEventCalendarEntryCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses
}); });
@@ -916,7 +916,7 @@ class _$SnEventCalendarEntryCopyWithImpl<$Res>
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable
as SnCheckInResult?,statuses: null == statuses ? _self.statuses : statuses // ignore: cast_nullable_to_non_nullable as SnCheckInResult?,statuses: null == statuses ? _self.statuses : statuses // ignore: cast_nullable_to_non_nullable
as List<dynamic>, as List<SnAccountStatus>,
)); ));
} }
/// Create a copy of SnEventCalendarEntry /// Create a copy of SnEventCalendarEntry
@@ -1010,7 +1010,7 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _SnEventCalendarEntry() when $default != null: case _SnEventCalendarEntry() when $default != null:
return $default(_that.date,_that.checkInResult,_that.statuses);case _: return $default(_that.date,_that.checkInResult,_that.statuses);case _:
@@ -1031,7 +1031,7 @@ return $default(_that.date,_that.checkInResult,_that.statuses);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnEventCalendarEntry(): case _SnEventCalendarEntry():
return $default(_that.date,_that.checkInResult,_that.statuses);} return $default(_that.date,_that.checkInResult,_that.statuses);}
@@ -1048,7 +1048,7 @@ return $default(_that.date,_that.checkInResult,_that.statuses);}
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnEventCalendarEntry() when $default != null: case _SnEventCalendarEntry() when $default != null:
return $default(_that.date,_that.checkInResult,_that.statuses);case _: return $default(_that.date,_that.checkInResult,_that.statuses);case _:
@@ -1063,13 +1063,13 @@ return $default(_that.date,_that.checkInResult,_that.statuses);case _:
@JsonSerializable() @JsonSerializable()
class _SnEventCalendarEntry implements SnEventCalendarEntry { class _SnEventCalendarEntry implements SnEventCalendarEntry {
const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List<dynamic> statuses}): _statuses = statuses; const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List<SnAccountStatus> statuses}): _statuses = statuses;
factory _SnEventCalendarEntry.fromJson(Map<String, dynamic> json) => _$SnEventCalendarEntryFromJson(json); factory _SnEventCalendarEntry.fromJson(Map<String, dynamic> json) => _$SnEventCalendarEntryFromJson(json);
@override final DateTime date; @override final DateTime date;
@override final SnCheckInResult? checkInResult; @override final SnCheckInResult? checkInResult;
final List<dynamic> _statuses; final List<SnAccountStatus> _statuses;
@override List<dynamic> get statuses { @override List<SnAccountStatus> get statuses {
if (_statuses is EqualUnmodifiableListView) return _statuses; if (_statuses is EqualUnmodifiableListView) return _statuses;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_statuses); return EqualUnmodifiableListView(_statuses);
@@ -1109,7 +1109,7 @@ abstract mixin class _$SnEventCalendarEntryCopyWith<$Res> implements $SnEventCal
factory _$SnEventCalendarEntryCopyWith(_SnEventCalendarEntry value, $Res Function(_SnEventCalendarEntry) _then) = __$SnEventCalendarEntryCopyWithImpl; factory _$SnEventCalendarEntryCopyWith(_SnEventCalendarEntry value, $Res Function(_SnEventCalendarEntry) _then) = __$SnEventCalendarEntryCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses
}); });
@@ -1131,7 +1131,7 @@ class __$SnEventCalendarEntryCopyWithImpl<$Res>
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable
as SnCheckInResult?,statuses: null == statuses ? _self._statuses : statuses // ignore: cast_nullable_to_non_nullable as SnCheckInResult?,statuses: null == statuses ? _self._statuses : statuses // ignore: cast_nullable_to_non_nullable
as List<dynamic>, as List<SnAccountStatus>,
)); ));
} }

View File

@@ -87,7 +87,10 @@ _SnEventCalendarEntry _$SnEventCalendarEntryFromJson(
: SnCheckInResult.fromJson( : SnCheckInResult.fromJson(
json['check_in_result'] as Map<String, dynamic>, json['check_in_result'] as Map<String, dynamic>,
), ),
statuses: json['statuses'] as List<dynamic>, statuses:
(json['statuses'] as List<dynamic>)
.map((e) => SnAccountStatus.fromJson(e as Map<String, dynamic>))
.toList(),
); );
Map<String, dynamic> _$SnEventCalendarEntryToJson( Map<String, dynamic> _$SnEventCalendarEntryToJson(
@@ -95,5 +98,5 @@ Map<String, dynamic> _$SnEventCalendarEntryToJson(
) => <String, dynamic>{ ) => <String, dynamic>{
'date': instance.date.toIso8601String(), 'date': instance.date.toIso8601String(),
'check_in_result': instance.checkInResult?.toJson(), 'check_in_result': instance.checkInResult?.toJson(),
'statuses': instance.statuses, 'statuses': instance.statuses.map((e) => e.toJson()).toList(),
}; };

View File

@@ -38,6 +38,7 @@ import 'package:island/screens/chat/chat.dart';
import 'package:island/screens/chat/room.dart'; import 'package:island/screens/chat/room.dart';
import 'package:island/screens/chat/room_detail.dart'; import 'package:island/screens/chat/room_detail.dart';
import 'package:island/screens/chat/call.dart'; import 'package:island/screens/chat/call.dart';
import 'package:island/screens/chat/search_messages_screen.dart';
import 'package:island/screens/creators/hub.dart'; import 'package:island/screens/creators/hub.dart';
import 'package:island/screens/creators/posts/post_manage_list.dart'; import 'package:island/screens/creators/posts/post_manage_list.dart';
import 'package:island/screens/creators/stickers/stickers.dart'; import 'package:island/screens/creators/stickers/stickers.dart';
@@ -555,6 +556,14 @@ final routerProvider = Provider<GoRouter>((ref) {
return ChatDetailScreen(id: id); return ChatDetailScreen(id: id);
}, },
), ),
GoRoute(
name: 'searchMessages',
path: '/chat/:id/search',
builder: (context, state) {
final id = state.pathParameters['id']!;
return SearchMessagesScreen(roomId: id);
},
),
], ],
), ),

View File

@@ -95,8 +95,24 @@ class LevelingScreen extends HookConsumerWidget {
title: Text('levelingProgress'.tr()), title: Text('levelingProgress'.tr()),
bottom: TabBar( bottom: TabBar(
tabs: [ tabs: [
Tab(text: 'leveling'.tr()), Tab(
Tab(text: 'stellarProgram'.tr()), child: Text(
'leveling'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'stellarProgram'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
], ],
), ),
), ),

View File

@@ -279,6 +279,24 @@ class AccountProfileScreen extends HookConsumerWidget {
if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName), if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName),
], ],
), ),
Tooltip(
message: 'creditsStatus'.tr(),
child: Row(
spacing: 6,
children: [
Icon(Symbols.star, size: 17, fill: 1).padding(right: 2),
Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'),
Text('·').bold(),
switch (data.profile.socialCreditsLevel) {
-1 => Text('socialCreditsLevelPoor').tr(),
0 => Text('socialCreditsLevelNormal').tr(),
1 => Text('socialCreditsLevelGood').tr(),
2 => Text('socialCreditsLevelExcellent').tr(),
_ => Text('unknown').tr(),
},
],
),
),
]; ];
} }

View File

@@ -72,6 +72,207 @@ class _AppLifecycleObserver extends WidgetsBindingObserver {
} }
} }
class _PublicRoomPreview extends HookConsumerWidget {
final String id;
final SnChatRoom room;
const _PublicRoomPreview({required this.id, required this.room});
@override
Widget build(BuildContext context, WidgetRef ref) {
final messages = ref.watch(messagesNotifierProvider(id));
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
final scrollController = useScrollController();
final listController = useMemoized(() => ListController(), []);
var isLoading = false;
// Add scroll listener for pagination
useEffect(() {
void onScroll() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
if (isLoading) return;
isLoading = true;
messagesNotifier.loadMore().then((_) => isLoading = false);
}
}
scrollController.addListener(onScroll);
return () => scrollController.removeListener(onScroll);
}, [scrollController]);
Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
SuperListView.builder(
listController: listController,
padding: EdgeInsets.symmetric(vertical: 16),
controller: scrollController,
reverse: true, // Show newest messages at the bottom
itemCount: messageList.length,
findChildIndexCallback: (key) {
final valueKey = key as ValueKey;
final messageId = valueKey.value as String;
return messageList.indexWhere((m) => m.id == messageId);
},
extentEstimation: (_, _) => 40,
itemBuilder: (context, index) {
final message = messageList[index];
final nextMessage =
index < messageList.length - 1 ? messageList[index + 1] : null;
final isLastInGroup =
nextMessage == null ||
nextMessage.senderId != message.senderId ||
nextMessage.createdAt
.difference(message.createdAt)
.inMinutes
.abs() >
3;
return MessageItem(
message: message,
isCurrentUser: false, // User is not a member, so not current user
onAction: null, // No actions allowed in preview mode
onJump: (_) {}, // No jump functionality in preview
progress: null,
showAvatar: isLastInGroup,
);
},
);
final compactHeader = isWideScreen(context);
Widget comfortHeaderWidget() => Column(
spacing: 4,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 26,
width: 26,
child:
(room.type == 1 && room.picture?.id == null)
? SplitAvatarWidget(
filesId:
room.members!
.map((e) => e.account.profile.picture?.id)
.toList(),
)
: room.picture?.id != null
? ProfilePictureWidget(
fileId: room.picture?.id,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
child: Text(
room.name![0].toUpperCase(),
style: const TextStyle(fontSize: 12),
),
),
),
Text(
(room.type == 1 && room.name == null)
? room.members!.map((e) => e.account.nick).join(', ')
: room.name!,
).fontSize(15),
],
);
Widget compactHeaderWidget() => Row(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 26,
width: 26,
child:
(room.type == 1 && room.picture?.id == null)
? SplitAvatarWidget(
filesId:
room.members!
.map((e) => e.account.profile.picture?.id)
.toList(),
)
: room.picture?.id != null
? ProfilePictureWidget(
fileId: room.picture?.id,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
child: Text(
room.name![0].toUpperCase(),
style: const TextStyle(fontSize: 12),
),
),
),
Text(
(room.type == 1 && room.name == null)
? room.members!.map((e) => e.account.nick).join(', ')
: room.name!,
).fontSize(19),
],
);
return AppScaffold(
appBar: AppBar(
leading: !compactHeader ? const Center(child: PageBackButton()) : null,
automaticallyImplyLeading: false,
toolbarHeight: compactHeader ? null : 64,
title: compactHeader ? compactHeaderWidget() : comfortHeaderWidget(),
actions: [
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {
context.pushNamed('chatDetail', pathParameters: {'id': id});
},
),
const Gap(8),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
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(),
),
),
),
// Join button at the bottom for public rooms
Container(
padding: const EdgeInsets.all(16),
child: FilledButton.tonalIcon(
onPressed: () async {
try {
showLoadingModal(context);
final apiClient = ref.read(apiClientProvider);
await apiClient.post('/sphere/chat/${room.id}/members/me');
ref.invalidate(chatroomIdentityProvider(id));
} catch (err) {
showErrorAlert(err);
} finally {
if (context.mounted) hideLoadingModal(context);
}
},
label: Text('chatJoin').tr(),
icon: const Icon(Icons.add),
),
),
],
),
);
}
}
@riverpod @riverpod
class MessagesNotifier extends _$MessagesNotifier { class MessagesNotifier extends _$MessagesNotifier {
late final Dio _apiClient; late final Dio _apiClient;
@@ -82,6 +283,9 @@ class MessagesNotifier extends _$MessagesNotifier {
final Map<String, LocalChatMessage> _pendingMessages = {}; final Map<String, LocalChatMessage> _pendingMessages = {};
final Map<String, Map<int, double>> _fileUploadProgress = {}; final Map<String, Map<int, double>> _fileUploadProgress = {};
int? _totalCount; int? _totalCount;
String? _searchQuery;
bool? _withLinks;
bool? _withAttachments;
late final String _roomId; late final String _roomId;
int _currentPage = 0; int _currentPage = 0;
@@ -96,28 +300,42 @@ class MessagesNotifier extends _$MessagesNotifier {
_database = ref.watch(databaseProvider); _database = ref.watch(databaseProvider);
final room = await ref.watch(chatroomProvider(roomId).future); final room = await ref.watch(chatroomProvider(roomId).future);
final identity = await ref.watch(chatroomIdentityProvider(roomId).future); final identity = await ref.watch(chatroomIdentityProvider(roomId).future);
if (room == null || identity == null) {
throw Exception('Room or identity not found'); if (room == null) {
throw Exception('Room not found');
} }
_room = room; _room = room;
_identity = identity;
// Allow building even if identity is null for public rooms
if (identity != null) {
_identity = identity;
}
developer.log( developer.log(
'MessagesNotifier built for room $roomId', 'MessagesNotifier built for room $roomId',
name: 'MessagesNotifier', name: 'MessagesNotifier',
); );
ref.listen(appLifecycleStateProvider, (_, next) { // Only setup sync and lifecycle listeners if user is a member
if (next.hasValue && next.value == AppLifecycleState.resumed) { if (identity != null) {
developer.log( ref.listen(appLifecycleStateProvider, (_, next) {
'App resumed, syncing messages', if (next.hasValue && next.value == AppLifecycleState.resumed) {
name: 'MessagesNotifier', developer.log(
); 'App resumed, syncing messages',
syncMessages(); name: 'MessagesNotifier',
} );
}); syncMessages();
}
});
}
return await loadInitial(); loadInitial();
return [];
}
List<LocalChatMessage> _sortMessages(List<LocalChatMessage> messages) {
messages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
return messages;
} }
Future<List<LocalChatMessage>> _getCachedMessages({ Future<List<LocalChatMessage>> _getCachedMessages({
@@ -128,13 +346,32 @@ class MessagesNotifier extends _$MessagesNotifier {
'Getting cached messages from offset $offset, take $take', 'Getting cached messages from offset $offset, take $take',
name: 'MessagesNotifier', name: 'MessagesNotifier',
); );
final dbMessages = await _database.getMessagesForRoom( final List<LocalChatMessage> dbMessages;
_roomId, if (_searchQuery != null && _searchQuery!.isNotEmpty) {
offset: offset, dbMessages = await _database.searchMessages(_roomId, _searchQuery ?? '');
limit: take, } else {
); final chatMessagesFromDb = await _database.getMessagesForRoom(
final dbLocalMessages = _roomId,
dbMessages.map(_database.companionToMessage).toList(); offset: offset,
limit: take,
);
dbMessages =
chatMessagesFromDb.map(_database.companionToMessage).toList();
}
List<LocalChatMessage> filteredMessages = dbMessages;
if (_withLinks == true) {
filteredMessages =
filteredMessages.where((msg) => _hasLink(msg)).toList();
}
if (_withAttachments == true) {
filteredMessages =
filteredMessages.where((msg) => _hasAttachment(msg)).toList();
}
final dbLocalMessages = filteredMessages;
if (offset == 0) { if (offset == 0) {
final pendingForRoom = final pendingForRoom =
@@ -143,7 +380,7 @@ class MessagesNotifier extends _$MessagesNotifier {
.toList(); .toList();
final allMessages = [...pendingForRoom, ...dbLocalMessages]; final allMessages = [...pendingForRoom, ...dbLocalMessages];
allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt)); _sortMessages(allMessages); // Use the helper function
final uniqueMessages = <LocalChatMessage>[]; final uniqueMessages = <LocalChatMessage>[];
final seenIds = <String>{}; final seenIds = <String>{};
@@ -218,7 +455,7 @@ class MessagesNotifier extends _$MessagesNotifier {
_isSyncing = true; _isSyncing = true;
developer.log('Starting message sync', name: 'MessagesNotifier'); developer.log('Starting message sync', name: 'MessagesNotifier');
ref.read(isSyncingProvider.notifier).state = true; Future.microtask(() => ref.read(isSyncingProvider.notifier).state = true);
try { try {
final dbMessages = await _database.getMessagesForRoom( final dbMessages = await _database.getMessagesForRoom(
_room.id, _room.id,
@@ -279,7 +516,9 @@ class MessagesNotifier extends _$MessagesNotifier {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {
developer.log('Finished message sync', name: 'MessagesNotifier'); developer.log('Finished message sync', name: 'MessagesNotifier');
ref.read(isSyncingProvider.notifier).state = false; Future.microtask(
() => ref.read(isSyncingProvider.notifier).state = false,
);
_isSyncing = false; _isSyncing = false;
} }
} }
@@ -290,7 +529,9 @@ class MessagesNotifier extends _$MessagesNotifier {
bool synced = false, bool synced = false,
}) async { }) async {
try { try {
if (offset == 0 && !synced) { if (offset == 0 &&
!synced &&
(_searchQuery == null || _searchQuery!.isEmpty)) {
_fetchAndCacheMessages(offset: 0, take: take).catchError((_) { _fetchAndCacheMessages(offset: 0, take: take).catchError((_) {
return <LocalChatMessage>[]; return <LocalChatMessage>[];
}); });
@@ -305,7 +546,11 @@ class MessagesNotifier extends _$MessagesNotifier {
return localMessages; return localMessages;
} }
return await _fetchAndCacheMessages(offset: offset, take: take); if (_searchQuery == null || _searchQuery!.isEmpty) {
return await _fetchAndCacheMessages(offset: offset, take: take);
} else {
return []; // If searching, and no local messages, don't fetch from network
}
} catch (e) { } catch (e) {
final localMessages = await _getCachedMessages( final localMessages = await _getCachedMessages(
offset: offset, offset: offset,
@@ -319,13 +564,15 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
} }
Future<List<LocalChatMessage>> loadInitial() async { Future<void> loadInitial() async {
developer.log('Loading initial messages', name: 'MessagesNotifier'); developer.log('Loading initial messages', name: 'MessagesNotifier');
syncMessages(); if (_searchQuery == null || _searchQuery!.isEmpty) {
syncMessages();
}
final messages = await _getCachedMessages(offset: 0, take: 100); final messages = await _getCachedMessages(offset: 0, take: 100);
_currentPage = 0; _currentPage = 0;
_hasMore = messages.length == _pageSize; _hasMore = messages.length == _pageSize;
return messages; state = AsyncValue.data(messages);
} }
Future<void> loadMore() async { Future<void> loadMore() async {
@@ -344,7 +591,9 @@ class MessagesNotifier extends _$MessagesNotifier {
_hasMore = false; _hasMore = false;
} }
state = AsyncValue.data([...currentMessages, ...newMessages]); state = AsyncValue.data(
_sortMessages([...currentMessages, ...newMessages]),
);
} catch (err, stackTrace) { } catch (err, stackTrace) {
developer.log( developer.log(
'Error loading more messages', 'Error loading more messages',
@@ -455,10 +704,13 @@ class MessagesNotifier extends _$MessagesNotifier {
final currentMessages = state.value ?? []; final currentMessages = state.value ?? [];
if (editingTo != null) { if (editingTo != null) {
final newMessages = currentMessages final newMessages =
.where((m) => m.id != localMessage.id) // remove pending message currentMessages
.map((m) => m.id == editingTo.id ? updatedMessage : m) // update original message .where((m) => m.id != localMessage.id) // remove pending message
.toList(); .map(
(m) => m.id == editingTo.id ? updatedMessage : m,
) // update original message
.toList();
state = AsyncValue.data(newMessages); state = AsyncValue.data(newMessages);
} else { } else {
final newMessages = final newMessages =
@@ -566,7 +818,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
return m; return m;
}).toList(); }).toList();
state = AsyncValue.data(newMessages); state = AsyncValue.data(_sortMessages(newMessages));
showErrorAlert(e); showErrorAlert(e);
} }
} }
@@ -626,7 +878,7 @@ class MessagesNotifier extends _$MessagesNotifier {
if (index >= 0) { if (index >= 0) {
final newList = [...currentMessages]; final newList = [...currentMessages];
newList[index] = updatedMessage; newList[index] = updatedMessage;
state = AsyncValue.data(newList); state = AsyncValue.data(_sortMessages(newList));
} }
} }
@@ -686,6 +938,20 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
} }
void searchMessages(String query, {bool? withLinks, bool? withAttachments}) {
_searchQuery = query.trim();
_withLinks = withLinks;
_withAttachments = withAttachments;
loadInitial();
}
void clearSearch() {
_searchQuery = null;
_withLinks = null;
_withAttachments = null;
loadInitial();
}
Future<LocalChatMessage?> fetchMessageById(String messageId) async { Future<LocalChatMessage?> fetchMessageById(String messageId) async {
developer.log( developer.log(
'Fetching message by id $messageId', 'Fetching message by id $messageId',
@@ -715,6 +981,18 @@ class MessagesNotifier extends _$MessagesNotifier {
rethrow; rethrow;
} }
} }
bool _hasLink(LocalChatMessage message) {
final content = message.toRemoteMessage().content;
if (content == null) return false;
final urlRegex = RegExp(r'https?://[^\s/$.?#].[^\s]*');
return urlRegex.hasMatch(content);
}
bool _hasAttachment(LocalChatMessage message) {
final remoteMessage = message.toRemoteMessage();
return remoteMessage.attachments.isNotEmpty;
}
} }
class ChatRoomScreen extends HookConsumerWidget { class ChatRoomScreen extends HookConsumerWidget {
@@ -734,57 +1012,77 @@ class ChatRoomScreen extends HookConsumerWidget {
); );
} else if (chatIdentity.value == null) { } else if (chatIdentity.value == null) {
// Identity was not found, user was not joined // Identity was not found, user was not joined
return AppScaffold( return chatRoom.when(
appBar: AppBar(leading: const PageBackButton()), data: (room) {
body: Center( if (room!.isPublic) {
child: // Show public room preview with messages but no input
ConstrainedBox( return _PublicRoomPreview(id: id, room: room);
constraints: const BoxConstraints(maxWidth: 280), } else {
child: Column( // Show regular "not joined" screen for private rooms
crossAxisAlignment: CrossAxisAlignment.center, return AppScaffold(
mainAxisAlignment: MainAxisAlignment.center, appBar: AppBar(leading: const PageBackButton()),
children: [ body: Center(
Icon( child:
chatRoom.value?.isCommunity == true ConstrainedBox(
? Symbols.person_add constraints: const BoxConstraints(maxWidth: 280),
: Symbols.person_remove, child: Column(
size: 36, crossAxisAlignment: CrossAxisAlignment.center,
fill: 1, mainAxisAlignment: MainAxisAlignment.center,
).padding(bottom: 4), children: [
Text('chatNotJoined').tr(), Icon(
if (chatRoom.value?.isCommunity != true) room.isCommunity == true
Text( ? Symbols.person_add
'chatUnableJoin', : Symbols.person_remove,
textAlign: TextAlign.center, size: 36,
).tr().bold() fill: 1,
else ).padding(bottom: 4),
FilledButton.tonalIcon( Text('chatNotJoined').tr(),
onPressed: () async { if (room.isCommunity != true)
try { Text(
showLoadingModal(context); 'chatUnableJoin',
final apiClient = ref.read(apiClientProvider); textAlign: TextAlign.center,
if (chatRoom.value == null) { ).tr().bold()
hideLoadingModal(context); else
return; FilledButton.tonalIcon(
} onPressed: () async {
try {
await apiClient.post( showLoadingModal(context);
'/sphere/chat/${chatRoom.value!.id}/members/me', final apiClient = ref.read(apiClientProvider);
); await apiClient.post(
ref.invalidate(chatroomIdentityProvider(id)); '/sphere/chat/${room.id}/members/me',
} catch (err) { );
showErrorAlert(err); ref.invalidate(chatroomIdentityProvider(id));
} finally { } catch (err) {
if (context.mounted) hideLoadingModal(context); showErrorAlert(err);
} } finally {
}, if (context.mounted) {
label: Text('chatJoin').tr(), hideLoadingModal(context);
icon: const Icon(Icons.add), }
).padding(top: 8), }
], },
), label: Text('chatJoin').tr(),
).center(), icon: const Icon(Icons.add),
), ).padding(top: 8),
],
),
).center(),
),
);
}
},
loading:
() => AppScaffold(
appBar: AppBar(leading: const PageBackButton()),
body: CircularProgressIndicator().center(),
),
error:
(error, _) => AppScaffold(
appBar: AppBar(leading: const PageBackButton()),
body: ResponseErrorWidget(
error: error,
onRetry: () => ref.refresh(chatroomProvider(id)),
),
),
); );
} }
@@ -1549,7 +1847,7 @@ class _ChatInput extends HookConsumerWidget {
children: [ children: [
IconButton( IconButton(
tooltip: 'stickers'.tr(), tooltip: 'stickers'.tr(),
icon: const Icon(Symbols.emoji_symbols), icon: const Icon(Symbols.add_reaction),
onPressed: () { onPressed: () {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
showStickerPickerPopover( showStickerPickerPopover(
@@ -1659,8 +1957,13 @@ class _ChatInput extends HookConsumerWidget {
horizontal: 12, horizontal: 12,
vertical: 4, vertical: 4,
), ),
counterText:
messageController.text.length > 1024
? '${messageController.text.length}/4096'
: null,
), ),
maxLines: null, maxLines: 3,
minLines: 1,
onTapOutside: onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(), (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),

View File

@@ -6,7 +6,7 @@ part of 'room.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$messagesNotifierHash() => r'32afe6ea24086d869cc47bd3389c8fd734409ca0'; String _$messagesNotifierHash() => r'fc3b66dfb8dd3fc55d142dae5c5e7bdc67eca5d4';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {

View File

@@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/screens/chat/chat.dart'; import 'package:island/screens/chat/chat.dart';
import 'package:island/widgets/account/account_pfc.dart';
import 'package:island/widgets/account/account_picker.dart'; import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
@@ -19,10 +20,17 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:island/pods/database.dart';
part 'room_detail.freezed.dart'; part 'room_detail.freezed.dart';
part 'room_detail.g.dart'; part 'room_detail.g.dart';
@riverpod
Future<int> totalMessagesCount(Ref ref, String roomId) async {
final database = ref.watch(databaseProvider);
return database.getTotalMessagesForRoom(roomId);
}
class ChatDetailScreen extends HookConsumerWidget { class ChatDetailScreen extends HookConsumerWidget {
final String id; final String id;
const ChatDetailScreen({super.key, required this.id}); const ChatDetailScreen({super.key, required this.id});
@@ -31,6 +39,7 @@ class ChatDetailScreen extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final roomState = ref.watch(chatroomProvider(id)); final roomState = ref.watch(chatroomProvider(id));
final roomIdentity = ref.watch(chatroomIdentityProvider(id)); final roomIdentity = ref.watch(chatroomIdentityProvider(id));
final totalMessages = ref.watch(totalMessagesCountProvider(id));
const kNotifyLevelText = [ const kNotifyLevelText = [
'chatNotifyLevelAll', 'chatNotifyLevelAll',
@@ -131,7 +140,7 @@ class ChatDetailScreen extends HookConsumerWidget {
const Text('chatBreakDescription').tr(), const Text('chatBreakDescription').tr(),
const Gap(16), const Gap(16),
ListTile( ListTile(
title: const Text('Clear').tr(), title: const Text('chatBreakClearButton').tr(),
subtitle: const Text('chatBreakClear').tr(), subtitle: const Text('chatBreakClear').tr(),
leading: const Icon(Icons.notifications_active), leading: const Icon(Icons.notifications_active),
onTap: () { onTap: () {
@@ -143,8 +152,8 @@ class ChatDetailScreen extends HookConsumerWidget {
}, },
), ),
ListTile( ListTile(
title: const Text('5m'), title: const Text('chatBreak5m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['5m']), subtitle: const Text('chatBreakHour').tr(args: ['chatBreak5m'.tr()]),
leading: const Icon(Symbols.circle), leading: const Icon(Symbols.circle),
onTap: () { onTap: () {
setChatBreak(now.add(const Duration(minutes: 5))); setChatBreak(now.add(const Duration(minutes: 5)));
@@ -155,8 +164,8 @@ class ChatDetailScreen extends HookConsumerWidget {
}, },
), ),
ListTile( ListTile(
title: const Text('10m'), title: const Text('chatBreak10m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['10m']), subtitle: const Text('chatBreakHour').tr(args: ['chatBreak10m'.tr()]),
leading: const Icon(Symbols.circle), leading: const Icon(Symbols.circle),
onTap: () { onTap: () {
setChatBreak(now.add(const Duration(minutes: 10))); setChatBreak(now.add(const Duration(minutes: 10)));
@@ -167,8 +176,8 @@ class ChatDetailScreen extends HookConsumerWidget {
}, },
), ),
ListTile( ListTile(
title: const Text('15m'), title: const Text('chatBreak15m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['15m']), subtitle: const Text('chatBreakHour').tr(args: ['chatBreak15m'.tr()]),
leading: const Icon(Symbols.timer_3), leading: const Icon(Symbols.timer_3),
onTap: () { onTap: () {
setChatBreak(now.add(const Duration(minutes: 15))); setChatBreak(now.add(const Duration(minutes: 15)));
@@ -179,8 +188,8 @@ class ChatDetailScreen extends HookConsumerWidget {
}, },
), ),
ListTile( ListTile(
title: const Text('30m'), title: const Text('chatBreak30m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['30m']), subtitle: const Text('chatBreakHour').tr(args: ['chatBreak30m'.tr()]),
leading: const Icon(Symbols.timer), leading: const Icon(Symbols.timer),
onTap: () { onTap: () {
setChatBreak(now.add(const Duration(minutes: 30))); setChatBreak(now.add(const Duration(minutes: 30)));
@@ -194,8 +203,8 @@ class ChatDetailScreen extends HookConsumerWidget {
TextField( TextField(
controller: durationController, controller: durationController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Custom (minutes)'.tr(), labelText: 'chatBreakCustomMinutes'.tr(),
hintText: 'Enter minutes'.tr(), hintText: 'chatBreakEnterMinutes'.tr(),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(Icons.check), icon: const Icon(Icons.check),
@@ -238,7 +247,7 @@ class ChatDetailScreen extends HookConsumerWidget {
return AppScaffold( return AppScaffold(
body: roomState.when( body: roomState.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')), error: (error, _) => Center(child: Text('errorGeneric'.tr(args: [error.toString()]))),
data: data:
(currentRoom) => CustomScrollView( (currentRoom) => CustomScrollView(
slivers: [ slivers: [
@@ -358,6 +367,22 @@ class ChatDetailScreen extends HookConsumerWidget {
: const Text('chatBreakNone').tr(), : const Text('chatBreakNone').tr(),
onTap: () => showChatBreakDialog(), onTap: () => showChatBreakDialog(),
), ),
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Icons.search),
trailing: const Icon(Symbols.chevron_right),
title: const Text('searchMessages').tr(),
subtitle: totalMessages.when(
data: (count) => Text('messagesCount'.tr(args: [count.toString()])),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('errorGeneric'.tr(args: [err.toString()])),
),
onTap: () {
context.pushNamed('searchMessages', pathParameters: {'id': id});
},
),
], ],
), ),
error: (_, _) => const SizedBox.shrink(), error: (_, _) => const SizedBox.shrink(),
@@ -666,8 +691,11 @@ class _ChatMemberListSheet extends HookConsumerWidget {
final member = data.items[index]; final member = data.items[index];
return ListTile( return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12), contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget( leading: AccountPfcGestureDetector(
fileId: member.account.profile.picture?.id, uname: member.account.name,
child: ProfilePictureWidget(
fileId: member.account.profile.picture?.id,
),
), ),
title: Row( title: Row(
spacing: 6, spacing: 6,
@@ -688,7 +716,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
? 'permissionModerator' ? 'permissionModerator'
: 'permissionMember', : 'permissionMember',
).tr(), ).tr(),
Text('·').bold().padding(horizontal: 6), Text('dotSeparator').bold().padding(horizontal: 6),
Expanded(child: Text("@${member.account.name}")), Expanded(child: Text("@${member.account.name}")),
], ],
), ),
@@ -848,7 +876,7 @@ class _ChatMemberRoleSheet extends HookConsumerWidget {
try { try {
final newRole = int.parse(roleController.text); final newRole = int.parse(roleController.text);
if (newRole < 0 || newRole > 100) { if (newRole < 0 || newRole > 100) {
throw 'Role must be between 0 and 100'; throw 'roleValidationHint'.tr();
} }
final apiClient = ref.read(apiClientProvider); final apiClient = ref.read(apiClientProvider);

View File

@@ -6,8 +6,8 @@ part of 'room_detail.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$chatMemberListNotifierHash() => String _$totalMessagesCountHash() =>
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973'; r'a15c03461f25c2d4d39c0926509bf626ae2550a6';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {
@@ -30,6 +30,128 @@ class _SystemHash {
} }
} }
/// See also [totalMessagesCount].
@ProviderFor(totalMessagesCount)
const totalMessagesCountProvider = TotalMessagesCountFamily();
/// See also [totalMessagesCount].
class TotalMessagesCountFamily extends Family<AsyncValue<int>> {
/// See also [totalMessagesCount].
const TotalMessagesCountFamily();
/// See also [totalMessagesCount].
TotalMessagesCountProvider call(String roomId) {
return TotalMessagesCountProvider(roomId);
}
@override
TotalMessagesCountProvider getProviderOverride(
covariant TotalMessagesCountProvider provider,
) {
return call(provider.roomId);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'totalMessagesCountProvider';
}
/// See also [totalMessagesCount].
class TotalMessagesCountProvider extends AutoDisposeFutureProvider<int> {
/// See also [totalMessagesCount].
TotalMessagesCountProvider(String roomId)
: this._internal(
(ref) => totalMessagesCount(ref as TotalMessagesCountRef, roomId),
from: totalMessagesCountProvider,
name: r'totalMessagesCountProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$totalMessagesCountHash,
dependencies: TotalMessagesCountFamily._dependencies,
allTransitiveDependencies:
TotalMessagesCountFamily._allTransitiveDependencies,
roomId: roomId,
);
TotalMessagesCountProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.roomId,
}) : super.internal();
final String roomId;
@override
Override overrideWith(
FutureOr<int> Function(TotalMessagesCountRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TotalMessagesCountProvider._internal(
(ref) => create(ref as TotalMessagesCountRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
roomId: roomId,
),
);
}
@override
AutoDisposeFutureProviderElement<int> createElement() {
return _TotalMessagesCountProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TotalMessagesCountProvider && other.roomId == roomId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, roomId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin TotalMessagesCountRef on AutoDisposeFutureProviderRef<int> {
/// The parameter `roomId` of this provider.
String get roomId;
}
class _TotalMessagesCountProviderElement
extends AutoDisposeFutureProviderElement<int>
with TotalMessagesCountRef {
_TotalMessagesCountProviderElement(super.provider);
@override
String get roomId => (origin as TotalMessagesCountProvider).roomId;
}
String _$chatMemberListNotifierHash() =>
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973';
abstract class _$ChatMemberListNotifier abstract class _$ChatMemberListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnChatMember>> { extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnChatMember>> {
late final String roomId; late final String roomId;

View File

@@ -0,0 +1,139 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/screens/chat/room.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat/message_item.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class SearchMessagesScreen extends HookConsumerWidget {
final String roomId;
const SearchMessagesScreen({super.key, required this.roomId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final searchController = useTextEditingController();
final withLinks = useState(false);
final withAttachments = useState(false);
final messagesNotifier = ref.read(
messagesNotifierProvider(roomId).notifier,
);
final messages = ref.watch(messagesNotifierProvider(roomId));
useEffect(() {
// Clear search when screen is disposed
return () {
messagesNotifier.clearSearch();
};
}, []);
return AppScaffold(
appBar: AppBar(title: const Text('searchMessages').tr()),
body: Column(
children: [
Column(
children: [
TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'searchMessagesHint'.tr(),
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.only(
left: 16,
right: 16,
top: 12,
bottom: 16,
),
suffix: IconButton(
iconSize: 18,
visualDensity: VisualDensity.compact,
icon: const Icon(Icons.clear),
onPressed: () {
searchController.clear();
messagesNotifier.clearSearch();
},
),
),
onChanged: (query) {
messagesNotifier.searchMessages(
query,
withLinks: withLinks.value,
withAttachments: withAttachments.value,
);
},
),
Row(
children: [
Expanded(
child: CheckboxListTile(
secondary: const Icon(Symbols.link),
title: const Text('searchLinks').tr(),
value: withLinks.value,
onChanged: (bool? value) {
withLinks.value = value!;
messagesNotifier.searchMessages(
searchController.text,
withLinks: withLinks.value,
withAttachments: withAttachments.value,
);
},
),
),
Expanded(
child: CheckboxListTile(
secondary: const Icon(Symbols.file_copy),
title: const Text('searchAttachments').tr(),
value: withAttachments.value,
onChanged: (bool? value) {
withAttachments.value = value!;
messagesNotifier.searchMessages(
searchController.text,
withLinks: withLinks.value,
withAttachments: withAttachments.value,
);
},
),
),
],
),
],
),
const Divider(height: 1),
Expanded(
child: messages.when(
data:
(messageList) =>
messageList.isEmpty
? Center(child: Text('noMessagesFound'.tr()))
: SuperListView.builder(
padding: const EdgeInsets.symmetric(vertical: 16),
reverse: true, // Show newest messages at the bottom
itemCount: messageList.length,
itemBuilder: (context, index) {
final message = messageList[index];
// Simplified MessageItem for search results, no grouping logic
return MessageItem(
message: message,
isCurrentUser:
false, // Or determine based on actual user
onAction: null,
onJump: (_) {},
progress: null,
showAvatar: true,
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('errorGeneric'.tr(args: [error.toString()]))),
),
),
],
),
);
}
}

View File

@@ -27,7 +27,9 @@ class AppDetailScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 2); final tabController = useTabController(initialLength: 2);
final appData = ref.watch(customAppProvider(publisherName, projectId, appId)); final appData = ref.watch(
customAppProvider(publisherName, projectId, appId),
);
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(
@@ -35,23 +37,43 @@ class AppDetailScreen extends HookConsumerWidget {
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Symbols.edit), icon: const Icon(Symbols.edit),
onPressed: appData.value == null onPressed:
? null appData.value == null
: () { ? null
context.pushNamed( : () {
'developerAppEdit', context.pushNamed(
pathParameters: { 'developerAppEdit',
'name': publisherName, pathParameters: {
'projectId': projectId, 'name': publisherName,
'id': appId, 'projectId': projectId,
}, 'id': appId,
); },
}, );
},
), ),
], ],
bottom: TabBar( bottom: TabBar(
controller: tabController, controller: tabController,
tabs: [Tab(text: 'overview'.tr()), Tab(text: 'secrets'.tr())], tabs: [
Tab(
child: Text(
'overview'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'secrets'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
), ),
), ),
body: appData.when( body: appData.when(
@@ -70,12 +92,14 @@ class AppDetailScreen extends HookConsumerWidget {
); );
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => ResponseErrorWidget( error:
error: err, (err, stack) => ResponseErrorWidget(
onRetry: () => ref.invalidate( error: err,
customAppProvider(publisherName, projectId, appId), onRetry:
), () => ref.invalidate(
), customAppProvider(publisherName, projectId, appId),
),
),
), ),
); );
} }
@@ -98,12 +122,13 @@ class _AppOverview extends StatelessWidget {
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
child: app.background != null child:
? CloudFileWidget( app.background != null
item: app.background!, ? CloudFileWidget(
fit: BoxFit.cover, item: app.background!,
) fit: BoxFit.cover,
: const SizedBox.shrink(), )
: const SizedBox.shrink(),
), ),
Positioned( Positioned(
left: 20, left: 20,

View File

@@ -52,7 +52,26 @@ class BotDetailScreen extends HookConsumerWidget {
], ],
bottom: TabBar( bottom: TabBar(
controller: tabController, controller: tabController,
tabs: [Tab(text: 'overview'.tr()), Tab(text: 'keys'.tr())], tabs: [
Tab(
child: Text(
'overview'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'keys'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
), ),
), ),
body: botData.when( body: botData.when(

View File

@@ -58,7 +58,26 @@ class ProjectDetailScreen extends HookConsumerWidget {
], ],
bottom: TabBar( bottom: TabBar(
controller: tabController, controller: tabController,
tabs: [Tab(text: 'customApps'.tr()), Tab(text: 'bots'.tr())], tabs: [
Tab(
child: Text(
'customApps'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'bots'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
), ),
), ),
body: TabBarView( body: TabBarView(

View File

@@ -143,8 +143,26 @@ class ArticlesScreen extends ConsumerWidget {
bottom: TabBar( bottom: TabBar(
isScrollable: true, isScrollable: true,
tabs: [ tabs: [
const Tab(text: 'All'), Tab(
...feeds.map((feed) => Tab(text: feed.title)), child: Text(
'All',
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
...feeds.map(
(feed) => Tab(
child: Text(
feed.title,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
),
], ],
), ),
), ),

View File

@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/services/color.dart'; import 'package:island/services/color.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/account_pfc.dart';
import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/post/post_list.dart'; import 'package:island/widgets/post/post_list.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';
@@ -658,8 +659,11 @@ class _RealmMemberListSheet extends HookConsumerWidget {
final member = data.items[index]; final member = data.items[index];
return ListTile( return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12), contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget( leading: AccountPfcGestureDetector(
fileId: member.account!.profile.picture?.id, uname: member.account!.name,
child: ProfilePictureWidget(
fileId: member.account!.profile.picture?.id,
),
), ),
title: Row( title: Row(
spacing: 6, spacing: 6,

View File

@@ -42,6 +42,16 @@ class AccountName extends StatelessWidget {
StellarMembershipMark(membership: account.perkSubscription!), StellarMembershipMark(membership: account.perkSubscription!),
if (account.profile.verification != null) if (account.profile.verification != null)
VerificationMark(mark: account.profile.verification!), VerificationMark(mark: account.profile.verification!),
if (account.automatedId != null)
Tooltip(
message: 'accountAutomated'.tr(),
child: Icon(
Symbols.smart_toy,
size: 16,
color: nameStyle.color,
fill: 1,
),
),
], ],
); );
} }
@@ -141,7 +151,7 @@ class VerificationStatusCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Icon( Icon(
mark.type == 4 mark.type == 4

View File

@@ -1,6 +1,7 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:flutter_popup_card/flutter_popup_card.dart'; import 'package:flutter_popup_card/flutter_popup_card.dart';
@@ -74,7 +75,42 @@ class AccountProfileCard extends HookConsumerWidget {
uname: data.name, uname: data.name,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
if (data.profile.timeZone.isNotEmpty) Tooltip(
message: 'creditsStatus'.tr(),
child: Row(
spacing: 6,
children: [
Icon(
Symbols.star,
size: 17,
fill: 1,
).padding(right: 2),
Text(
'${data.profile.socialCredits.toStringAsFixed(2)} pts',
).fontSize(12),
switch (data.profile.socialCreditsLevel) {
-1 => Text('socialCreditsLevelPoor').tr(),
0 => Text('socialCreditsLevelNormal').tr(),
1 => Text('socialCreditsLevelGood').tr(),
2 => Text('socialCreditsLevelExcellent').tr(),
_ => Text('unknown').tr(),
}.fontSize(12),
],
),
),
if (data.automatedId != null)
Row(
spacing: 6,
children: [
Icon(
Symbols.smart_toy,
size: 17,
fill: 1,
).padding(right: 2),
Text('accountAutomated').tr().fontSize(12),
],
),
if (data.profile.timeZone.isNotEmpty && !kIsWeb)
Row( Row(
spacing: 6, spacing: 6,
children: [ children: [

View File

@@ -2,7 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart';
import 'package:island/models/activity.dart'; import 'package:island/models/activity.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:island/widgets/account/event_details_widget.dart'; import 'package:island/widgets/account/event_details_widget.dart';
import 'package:table_calendar/table_calendar.dart'; import 'package:table_calendar/table_calendar.dart';
@@ -87,24 +89,56 @@ class EventCalendarWidget extends HookConsumerWidget {
return Center(child: Text(text)); return Center(child: Text(text));
}, },
markerBuilder: (context, day, events) { markerBuilder: (context, day, events) {
var checkInResult = final checkInResult =
events.whereType<SnCheckInResult>().firstOrNull; events.whereType<SnCheckInResult>().firstOrNull;
final statuses = events.whereType<SnAccountStatus>().toList();
final textColor =
isSameDay(selectedDay.value, day)
? Colors.white
: isSameDay(DateTime.now(), day)
? Colors.white
: Theme.of(context).colorScheme.onSurface;
final shadow =
isSameDay(selectedDay.value, day) ||
isSameDay(DateTime.now(), day)
? [
Shadow(
color: Colors.black.withOpacity(0.5),
offset: const Offset(0, 1),
blurRadius: 4,
),
]
: null;
if (checkInResult != null) { if (checkInResult != null) {
return Positioned( return Positioned(
top: 32, top: 32,
child: Text( child: Row(
'checkInResultT${checkInResult.level}'.tr(), spacing: 2,
style: TextStyle( children: [
fontSize: 9, Text(
color: 'checkInResultT${checkInResult.level}'.tr(),
isSameDay(selectedDay.value, day) style: TextStyle(
? Theme.of(context).colorScheme.onPrimaryContainer fontSize: 9,
: isSameDay(DateTime.now(), day) color: textColor,
? Theme.of( shadows: shadow,
context, ),
).colorScheme.onSecondaryContainer ),
: Theme.of(context).colorScheme.onSurface, if (statuses.isNotEmpty) ...[
), Icon(
switch (statuses.first.attitude) {
0 => Symbols.sentiment_satisfied,
2 => Symbols.sentiment_dissatisfied,
_ => Symbols.sentiment_neutral,
},
size: 12,
color: textColor,
shadows: shadow,
),
],
],
), ),
); );
} }

View File

@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:island/models/activity.dart'; import 'package:island/models/activity.dart';
import 'package:island/services/time.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@@ -53,6 +54,33 @@ class EventDetailsWidget extends StatelessWidget {
), ),
], ],
).padding(top: 8), ).padding(top: 8),
if (event!.statuses.isNotEmpty) ...[
const Gap(16),
Text('statusLabel').tr().fontSize(16).bold(),
],
for (final status in event!.statuses) ...[
Row(
spacing: 8,
children: [
Icon(switch (status.attitude) {
0 => Symbols.sentiment_satisfied,
2 => Symbols.sentiment_dissatisfied,
_ => Symbols.sentiment_neutral,
}),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(status.label),
Text(
'${status.createdAt.formatSystem()} - ${status.clearedAt?.formatSystem() ?? 'present'.tr()}',
).fontSize(11).opacity(0.8),
],
),
),
],
).padding(vertical: 8),
],
], ],
), ),
if (event?.checkInResult == null && (event?.statuses.isEmpty ?? true)) if (event?.checkInResult == null && (event?.statuses.isEmpty ?? true))

View File

@@ -1,9 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui'; import 'dart:ui';
import 'package:dismissible_page/dismissible_page.dart'; import 'package:dismissible_page/dismissible_page.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:file_saver/file_saver.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:flutter_blurhash/flutter_blurhash.dart';
@@ -321,7 +324,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
Future<void> saveToGallery() async { Future<void> saveToGallery() async {
try { try {
// Show loading indicator // Show loading indicator
showSnackBar('Saving image to gallery...'); showSnackBar('Saving image...');
// Get the image URL // Get the image URL
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
@@ -339,10 +342,18 @@ class CloudFileZoomIn extends HookConsumerWidget {
filePath, filePath,
queryParameters: {'original': true}, queryParameters: {'original': true},
); );
await Gal.putImage(filePath, album: 'Solar Network'); if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
// Save to gallery
// Show success message await Gal.putImage(filePath, album: 'Solar Network');
showSnackBar('Image saved to gallery'); // Show success message
showSnackBar('Image saved to gallery');
} else {
await FileSaver.instance.saveFile(
name: item.name.isEmpty ? '${item.id}.$extName' : item.name,
file: File(filePath),
);
showSnackBar('Image saved to $filePath');
}
} catch (e) { } catch (e) {
showErrorAlert(e); showErrorAlert(e);
} }
@@ -437,7 +448,24 @@ class CloudFileZoomIn extends HookConsumerWidget {
).padding(horizontal: 24, vertical: 16), ).padding(horizontal: 24, vertical: 16),
const Divider(height: 1), const Divider(height: 1),
ListTile( ListTile(
leading: const Icon(Icons.file_present), leading: const Icon(Symbols.tag),
title: Text('ID').tr(),
subtitle: Text(
item.id,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
trailing: IconButton(
icon: const Icon(Icons.copy),
onPressed: () {
Clipboard.setData(ClipboardData(text: item.id));
showSnackBar('File ID copied to clipboard');
},
),
),
ListTile(
leading: const Icon(Symbols.file_present),
title: Text('Name').tr(), title: Text('Name').tr(),
subtitle: Text( subtitle: Text(
item.name, item.name,
@@ -623,6 +651,10 @@ class CloudFileZoomIn extends HookConsumerWidget {
); );
} }
final shadow = [
Shadow(color: Colors.black54, blurRadius: 5.0, offset: Offset(1.0, 1.0)),
];
return DismissiblePage( return DismissiblePage(
isFullScreen: true, isFullScreen: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@@ -660,22 +692,17 @@ class CloudFileZoomIn extends HookConsumerWidget {
children: [ children: [
Row( Row(
children: [ children: [
IconButton( if (!kIsWeb)
icon: Icon( IconButton(
Icons.save_alt, icon: Icon(
color: Colors.white, Icons.save_alt,
shadows: [ color: Colors.white,
Shadow( shadows: shadow,
color: Colors.black54, ),
blurRadius: 5.0, onPressed: () async {
offset: Offset(1.0, 1.0), saveToGallery();
), },
],
), ),
onPressed: () async {
saveToGallery();
},
),
IconButton( IconButton(
onPressed: () { onPressed: () {
showOriginal.value = !showOriginal.value; showOriginal.value = !showOriginal.value;
@@ -683,29 +710,13 @@ class CloudFileZoomIn extends HookConsumerWidget {
icon: Icon( icon: Icon(
showOriginal.value ? Symbols.hd : Symbols.sd, showOriginal.value ? Symbols.hd : Symbols.sd,
color: Colors.white, color: Colors.white,
shadows: [ shadows: shadow,
Shadow(
color: Colors.black54,
blurRadius: 5.0,
offset: Offset(1.0, 1.0),
),
],
), ),
), ),
], ],
), ),
IconButton( IconButton(
icon: Icon( icon: Icon(Icons.close, color: Colors.white, shadows: shadow),
Icons.close,
color: Colors.white,
shadows: [
Shadow(
color: Colors.black54,
blurRadius: 5.0,
offset: Offset(1.0, 1.0),
),
],
),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
], ],
@@ -722,26 +733,24 @@ class CloudFileZoomIn extends HookConsumerWidget {
icon: Icon( icon: Icon(
Icons.info_outline, Icons.info_outline,
color: Colors.white, color: Colors.white,
shadows: [ shadows: shadow,
Shadow(
color: Colors.black54,
blurRadius: 5.0,
offset: Offset(1.0, 1.0),
),
],
), ),
onPressed: showInfoSheet, onPressed: showInfoSheet,
), ),
Spacer(), Spacer(),
IconButton( IconButton(
icon: Icon(Icons.remove, color: Colors.white), icon: Icon(
Icons.remove,
color: Colors.white,
shadows: shadow,
),
onPressed: () { onPressed: () {
photoViewController.scale = photoViewController.scale =
(photoViewController.scale ?? 1) - 0.05; (photoViewController.scale ?? 1) - 0.05;
}, },
), ),
IconButton( IconButton(
icon: Icon(Icons.add, color: Colors.white), icon: Icon(Icons.add, color: Colors.white, shadows: shadow),
onPressed: () { onPressed: () {
photoViewController.scale = photoViewController.scale =
(photoViewController.scale ?? 1) + 0.05; (photoViewController.scale ?? 1) + 0.05;
@@ -752,13 +761,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
icon: Icon( icon: Icon(
Icons.rotate_left, Icons.rotate_left,
color: Colors.white, color: Colors.white,
shadows: [ shadows: shadow,
Shadow(
color: Colors.black54,
blurRadius: 5.0,
offset: Offset(1.0, 1.0),
),
],
), ),
onPressed: () { onPressed: () {
rotation.value = (rotation.value - 1) % 4; rotation.value = (rotation.value - 1) % 4;

View File

@@ -83,32 +83,32 @@ class PostPinSheet extends HookConsumerWidget {
), ),
// Reply page pin option (show always, but disabled when not available) // Reply page pin option (show always, but disabled when not available)
ListTile( // Disabled for now because im being lazy
leading: Radio<int>( // ListTile(
value: 2, // leading: Radio<int>(
groupValue: mode.value, // value: 2,
onChanged: // groupValue: mode.value,
post.repliedPostId != null && post.repliedPostId!.isNotEmpty // onChanged:
? (value) { // post.repliedPostId != null && post.repliedPostId!.isNotEmpty
mode.value = value!; // ? (value) {
} // mode.value = value!;
: null, // }
), // : null,
title: Text('replyPage'.tr()), // ),
subtitle: // title: Text('replyPage'.tr()),
post.repliedPostId != null && post.repliedPostId!.isNotEmpty // subtitle:
? Text('pinPostReplyHint'.tr()) // post.repliedPostId != null && post.repliedPostId!.isNotEmpty
: Text('pinPostReplyDisabledHint'.tr()), // ? Text('pinPostReplyHint'.tr())
onTap: // : Text('pinPostReplyDisabledHint'.tr()),
post.repliedPostId != null && post.repliedPostId!.isNotEmpty // onTap:
? () { // post.repliedPostId != null && post.repliedPostId!.isNotEmpty
mode.value = 2; // ? () {
} // mode.value = 2;
: null, // }
enabled: // : null,
post.repliedPostId != null && post.repliedPostId!.isNotEmpty, // enabled:
), // post.repliedPostId != null && post.repliedPostId!.isNotEmpty,
// ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Pin button // Pin button

View File

@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <bitsdojo_window_linux/bitsdojo_window_plugin.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 <file_selector_linux/file_selector_plugin.h>
#include <flutter_platform_alert/flutter_platform_alert_plugin.h> #include <flutter_platform_alert/flutter_platform_alert_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
@@ -28,6 +29,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar = g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar); 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);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_linux bitsdojo_window_linux
file_saver
file_selector_linux file_selector_linux
flutter_platform_alert flutter_platform_alert
flutter_secure_storage_linux flutter_secure_storage_linux

View File

@@ -9,6 +9,7 @@ import bitsdojo_window_macos
import connectivity_plus import connectivity_plus
import device_info_plus import device_info_plus
import file_picker import file_picker
import file_saver
import file_selector_macos import file_selector_macos
import firebase_analytics import firebase_analytics
import firebase_core import firebase_core
@@ -45,6 +46,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseAnalyticsPlugin")) FirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseAnalyticsPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))

View File

@@ -9,6 +9,8 @@ PODS:
- FlutterMacOS - FlutterMacOS
- file_picker (0.0.1): - file_picker (0.0.1):
- FlutterMacOS - FlutterMacOS
- file_saver (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1): - file_selector_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- Firebase/CoreOnly (12.0.0): - Firebase/CoreOnly (12.0.0):
@@ -249,6 +251,7 @@ DEPENDENCIES:
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`) - croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`) - firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`)
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
@@ -315,6 +318,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_picker: file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
file_saver:
:path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos
file_selector_macos: file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
firebase_analytics: firebase_analytics:
@@ -384,6 +389,7 @@ SPEC CHECKSUMS:
croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43 croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
firebase_analytics: 53f0dc87ad10f56a6df8746da60d8a5fe41f886f firebase_analytics: 53f0dc87ad10f56a6df8746da60d8a5fe41f886f

View File

@@ -569,6 +569,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.3.2" version: "10.3.2"
file_saver:
dependency: "direct main"
description:
name: file_saver
sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
file_selector_linux: file_selector_linux:
dependency: transitive dependency: transitive
description: description:
@@ -1921,10 +1929,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: record_ios name: record_ios
sha256: "895c9467faec72d8e718a3142b51114958f42f18053836a8b484a74f9372f51a" sha256: "13e241ed9cbc220534a40ae6b66222e21288db364d96dd66fb762ebd3cb77c71"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
record_linux: record_linux:
dependency: transitive dependency: transitive
description: description:
@@ -2041,10 +2049,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: screen_brightness_android name: screen_brightness_android
sha256: fb5fa43cb89d0c9b8534556c427db1e97e46594ac5d66ebdcf16063b773d54ed sha256: d34f5321abd03bc3474f4c381f53d189117eba0b039eac1916aa92cca5fd0a96
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
screen_brightness_platform_interface: screen_brightness_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -2640,10 +2648,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: watcher name: watcher
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.3"
waveform_flutter: waveform_flutter:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 3.2.0+128 version: 3.2.0+129
environment: environment:
sdk: ^3.7.2 sdk: ^3.7.2
@@ -139,6 +139,7 @@ dependencies:
material_color_utilities: ^0.11.1 material_color_utilities: ^0.11.1
screenshot: ^3.0.0 screenshot: ^3.0.0
flutter_card_swiper: ^7.0.2 flutter_card_swiper: ^7.0.2
file_saver: ^0.3.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -235,4 +236,5 @@ msix_config:
identity_name: dev.solian.app identity_name: dev.solian.app
msix_version: 3.2.0.0 msix_version: 3.2.0.0
logo_path: .\assets\icons\icon.png logo_path: .\assets\icons\icon.png
capabilities: internetClientServer, location, microphone, webcam capabilities: internetClientServer, location, microphone, webcam

View File

@@ -8,6 +8,7 @@
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h> #include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_saver/file_saver_plugin.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <firebase_core/firebase_core_plugin_c_api.h> #include <firebase_core/firebase_core_plugin_c_api.h>
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h> #include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
@@ -35,6 +36,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar( ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSaverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSaverPlugin"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
FirebaseCorePluginCApiRegisterWithRegistrar( FirebaseCorePluginCApiRegisterWithRegistrar(

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_windows bitsdojo_window_windows
connectivity_plus connectivity_plus
file_saver
file_selector_windows file_selector_windows
firebase_core firebase_core
flutter_inappwebview_windows flutter_inappwebview_windows