Compare commits

...

11 Commits

Author SHA1 Message Date
3b56b94242 🚀 Launch 1.2.1+30 2024-09-09 13:07:21 +08:00
34043e722b Able to play music 2024-09-08 22:43:01 +08:00
e4a5ac9d0a 🚑 Fix cannot load ongoing call 2024-09-08 16:03:50 +08:00
c991590b27 🚀 Launch 1.2.1+28 2024-09-08 12:47:27 +08:00
d8b2c7f81e User experience & level 2024-09-08 12:32:21 +08:00
1fd042bcae 🐛 Bug fixes on profile page 2024-09-08 01:48:01 +08:00
0a04c72468 Daily sign history 2024-09-08 00:23:59 +08:00
4e8f2ddef3 🎨 Use SizedBox.shrink instead of empty SizedBox for placeholder 2024-09-07 17:48:07 +08:00
85f97521e5 🎨 Use Gap instead of empty SizedBox 2024-09-07 17:45:44 +08:00
9c451f485a 🎨 Refactored all models with json annotation to make code cleaner 2024-09-07 15:36:06 +08:00
f836b22c73 💄 Remove message ripple effect 2024-09-06 23:55:44 +08:00
98 changed files with 2264 additions and 1236 deletions

View File

@ -22,7 +22,8 @@
android:label="Solian"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true">
android:supportsRtl="true"
android:usesCleartextTraffic="true">
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false"

View File

@ -1,4 +1,6 @@
PODS:
- audio_session (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- FlutterMacOS
@ -221,15 +223,11 @@ PODS:
- TOCropViewController (~> 2.7.4)
- image_picker_ios (0.0.1):
- Flutter
- just_audio (0.0.1):
- Flutter
- livekit_client (2.2.4):
- Flutter
- WebRTC-SDK (= 125.6422.04)
- media_kit_libs_ios_video (1.0.4):
- Flutter
- media_kit_native_event_loop (1.0.0):
- Flutter
- media_kit_video (0.0.1):
- Flutter
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
@ -251,8 +249,6 @@ PODS:
- PromisesObjC (= 2.4.0)
- protocol_handler_ios (0.0.1):
- Flutter
- screen_brightness_ios (0.1.0):
- Flutter
- SDWebImage (5.19.7):
- SDWebImage/Core (= 5.19.7)
- SDWebImage/Core (5.19.7)
@ -268,13 +264,15 @@ PODS:
- TOCropViewController (2.7.4)
- url_launcher_ios (0.0.1):
- Flutter
- volume_controller (0.0.1):
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- Flutter
- WebRTC-SDK (125.6422.04)
DEPENDENCIES:
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
@ -291,22 +289,19 @@ DEPENDENCIES:
- gal (from `.symlinks/plugins/gal/darwin`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- just_audio (from `.symlinks/plugins/just_audio/ios`)
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
- protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`)
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
@ -339,6 +334,8 @@ SPEC REPOS:
- WebRTC-SDK
EXTERNAL SOURCES:
audio_session:
:path: ".symlinks/plugins/audio_session/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/darwin"
device_info_plus:
@ -371,14 +368,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_cropper/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
just_audio:
:path: ".symlinks/plugins/just_audio/ios"
livekit_client:
:path: ".symlinks/plugins/livekit_client/ios"
media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_native_event_loop:
:path: ".symlinks/plugins/media_kit_native_event_loop/ios"
media_kit_video:
:path: ".symlinks/plugins/media_kit_video/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
pasteboard:
@ -391,8 +384,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/pointer_interceptor_ios/ios"
protocol_handler_ios:
:path: ".symlinks/plugins/protocol_handler_ios/ios"
screen_brightness_ios:
:path: ".symlinks/plugins/screen_brightness_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
@ -401,12 +392,13 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
volume_controller:
:path: ".symlinks/plugins/volume_controller/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
@ -442,10 +434,8 @@ SPEC CHECKSUMS:
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
livekit_client: d079c5f040d4bf2b80440ff0ae997725a183e4bc
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0
@ -455,7 +445,6 @@ SPEC CHECKSUMS:
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
@ -463,7 +452,7 @@ SPEC CHECKSUMS:
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
@ -197,7 +198,7 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
height: 24,
child: CircularProgressIndicator(strokeWidth: 3),
),
const SizedBox(height: 12),
const Gap(12),
CenteredContainer(
maxWidth: 280,
child: Column(

View File

@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:media_kit/media_kit.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:provider/provider.dart';
import 'package:solian/bootstrapper.dart';
@ -35,7 +34,6 @@ import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
await Future.wait([
_initializeFirebase(),
@ -109,7 +107,7 @@ class SolianApp extends StatelessWidget {
return SystemShell(
child: ScaffoldMessenger(
child: BootstrapperShell(
child: child ?? const SizedBox(),
child: child ?? const SizedBox.shrink(),
),
),
);

View File

@ -1,3 +1,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'account.g.dart';
@JsonSerializable()
class Account {
int id;
DateTime createdAt;
@ -10,6 +15,7 @@ class Account {
dynamic avatar;
dynamic banner;
String description;
AccountProfile? profile;
List<AccountBadge>? badges;
String? emailAddress;
int? externalId;
@ -26,55 +32,19 @@ class Account {
required this.avatar,
required this.banner,
required this.description,
required this.profile,
required this.badges,
required this.emailAddress,
this.externalId,
});
factory Account.fromJson(Map<String, dynamic> json) => Account(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
confirmedAt: json['confirmed_at'] != null
? DateTime.parse(json['confirmed_at'])
: null,
suspendedAt: json['suspended_at'] != null
? DateTime.parse(json['suspended_at'])
: null,
name: json['name'],
nick: json['nick'],
avatar: json['avatar'],
banner: json['banner'],
description: json['description'],
emailAddress: json['email_address'],
badges: json['badges']
?.map((e) => AccountBadge.fromJson(e))
.toList()
.cast<AccountBadge>(),
externalId: json['external_id'],
);
factory Account.fromJson(Map<String, dynamic> json) =>
_$AccountFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'confirmed_at': confirmedAt?.toIso8601String(),
'suspended_at': suspendedAt?.toIso8601String(),
'name': name,
'nick': nick,
'avatar': avatar,
'banner': banner,
'description': description,
'email_address': emailAddress,
'badges': badges?.map((e) => e.toJson()).toList(),
'external_id': externalId,
};
Map<String, dynamic> toJson() => _$AccountToJson(this);
}
@JsonSerializable()
class AccountBadge {
int id;
DateTime createdAt;
@ -94,25 +64,40 @@ class AccountBadge {
required this.type,
});
factory AccountBadge.fromJson(Map<String, dynamic> json) => AccountBadge(
id: json['id'],
accountId: json['account_id'],
updatedAt: DateTime.parse(json['updated_at']),
createdAt: DateTime.parse(json['created_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
metadata: json['metadata'],
type: json['type'],
);
factory AccountBadge.fromJson(Map<String, dynamic> json) =>
_$AccountBadgeFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'account_id': accountId,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'metadata': metadata,
'type': type,
};
Map<String, dynamic> toJson() => _$AccountBadgeToJson(this);
}
@JsonSerializable()
class AccountProfile {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
String? firstName;
String? lastName;
int? experience;
DateTime? lastSeenAt;
DateTime? birthday;
int accountId;
AccountProfile({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.firstName,
required this.lastName,
required this.experience,
required this.lastSeenAt,
required this.birthday,
required this.accountId,
});
factory AccountProfile.fromJson(Map<String, dynamic> json) =>
_$AccountProfileFromJson(json);
Map<String, dynamic> toJson() => _$AccountProfileToJson(this);
}

110
lib/models/account.g.dart Normal file
View File

@ -0,0 +1,110 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Account _$AccountFromJson(Map<String, dynamic> json) => Account(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
confirmedAt: json['confirmed_at'] == null
? null
: DateTime.parse(json['confirmed_at'] as String),
suspendedAt: json['suspended_at'] == null
? null
: DateTime.parse(json['suspended_at'] as String),
name: json['name'] as String,
nick: json['nick'] as String,
avatar: json['avatar'],
banner: json['banner'],
description: json['description'] as String,
profile: json['profile'] == null
? null
: AccountProfile.fromJson(json['profile'] as Map<String, dynamic>),
badges: (json['badges'] as List<dynamic>?)
?.map((e) => AccountBadge.fromJson(e as Map<String, dynamic>))
.toList(),
emailAddress: json['email_address'] as String?,
externalId: (json['external_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$AccountToJson(Account instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'confirmed_at': instance.confirmedAt?.toIso8601String(),
'suspended_at': instance.suspendedAt?.toIso8601String(),
'name': instance.name,
'nick': instance.nick,
'avatar': instance.avatar,
'banner': instance.banner,
'description': instance.description,
'profile': instance.profile?.toJson(),
'badges': instance.badges?.map((e) => e.toJson()).toList(),
'email_address': instance.emailAddress,
'external_id': instance.externalId,
};
AccountBadge _$AccountBadgeFromJson(Map<String, dynamic> json) => AccountBadge(
id: (json['id'] as num).toInt(),
accountId: (json['account_id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
metadata: json['metadata'] as Map<String, dynamic>?,
type: json['type'] as String,
);
Map<String, dynamic> _$AccountBadgeToJson(AccountBadge instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'metadata': instance.metadata,
'type': instance.type,
'account_id': instance.accountId,
};
AccountProfile _$AccountProfileFromJson(Map<String, dynamic> json) =>
AccountProfile(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
firstName: json['first_name'] as String?,
lastName: json['last_name'] as String?,
experience: (json['experience'] as num?)?.toInt(),
lastSeenAt: json['last_seen_at'] == null
? null
: DateTime.parse(json['last_seen_at'] as String),
birthday: json['birthday'] == null
? null
: DateTime.parse(json['birthday'] as String),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$AccountProfileToJson(AccountProfile instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'first_name': instance.firstName,
'last_name': instance.lastName,
'experience': instance.experience,
'last_seen_at': instance.lastSeenAt?.toIso8601String(),
'birthday': instance.birthday?.toIso8601String(),
'account_id': instance.accountId,
};

View File

@ -1,3 +1,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'account_status.g.dart';
@JsonSerializable()
class AccountStatus {
bool isDisturbable;
bool isOnline;
@ -11,21 +16,13 @@ class AccountStatus {
required this.status,
});
factory AccountStatus.fromJson(Map<String, dynamic> json) => AccountStatus(
isDisturbable: json['is_disturbable'],
isOnline: json['is_online'],
lastSeenAt: json['last_seen_at'] != null ? DateTime.parse(json['last_seen_at']) : null,
status: json['status'] != null ? Status.fromJson(json['status']) : null,
);
factory AccountStatus.fromJson(Map<String, dynamic> json) =>
_$AccountStatusFromJson(json);
Map<String, dynamic> toJson() => {
'is_disturbable': isDisturbable,
'is_online': isOnline,
'last_seen_at': lastSeenAt?.toIso8601String(),
'status': status?.toJson(),
};
Map<String, dynamic> toJson() => _$AccountStatusToJson(this);
}
@JsonSerializable()
class Status {
int id;
DateTime createdAt;
@ -53,31 +50,7 @@ class Status {
required this.accountId,
});
factory Status.fromJson(Map<String, dynamic> json) => Status(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null ? DateTime.parse(json['deleted_at']) : null,
type: json['type'],
label: json['label'],
attitude: json['attitude'],
isNoDisturb: json['is_no_disturb'],
isInvisible: json['is_invisible'],
clearAt: json['clear_at'] != null ? DateTime.parse(json['clear_at']) : null,
accountId: json['account_id'],
);
factory Status.fromJson(Map<String, dynamic> json) => _$StatusFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'type': type,
'label': label,
'attitude': attitude,
'is_no_disturb': isNoDisturb,
'is_invisible': isInvisible,
'clear_at': clearAt?.toIso8601String(),
'account_id': accountId,
};
Map<String, dynamic> toJson() => _$StatusToJson(this);
}

View File

@ -0,0 +1,59 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account_status.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AccountStatus _$AccountStatusFromJson(Map<String, dynamic> json) =>
AccountStatus(
isDisturbable: json['is_disturbable'] as bool,
isOnline: json['is_online'] as bool,
lastSeenAt: json['last_seen_at'] == null
? null
: DateTime.parse(json['last_seen_at'] as String),
status: json['status'] == null
? null
: Status.fromJson(json['status'] as Map<String, dynamic>),
);
Map<String, dynamic> _$AccountStatusToJson(AccountStatus instance) =>
<String, dynamic>{
'is_disturbable': instance.isDisturbable,
'is_online': instance.isOnline,
'last_seen_at': instance.lastSeenAt?.toIso8601String(),
'status': instance.status?.toJson(),
};
Status _$StatusFromJson(Map<String, dynamic> json) => Status(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
type: json['type'] as String,
label: json['label'] as String,
attitude: (json['attitude'] as num).toInt(),
isNoDisturb: json['is_no_disturb'] as bool,
isInvisible: json['is_invisible'] as bool,
clearAt: json['clear_at'] == null
? null
: DateTime.parse(json['clear_at'] as String),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$StatusToJson(Status instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'type': instance.type,
'label': instance.label,
'attitude': instance.attitude,
'is_no_disturb': instance.isNoDisturb,
'is_invisible': instance.isInvisible,
'clear_at': instance.clearAt?.toIso8601String(),
'account_id': instance.accountId,
};

View File

@ -1,5 +1,9 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:solian/models/account.dart';
part 'attachment.g.dart';
@JsonSerializable()
class AttachmentPlaceholder {
int chunkCount;
int chunkSize;
@ -12,19 +16,12 @@ class AttachmentPlaceholder {
});
factory AttachmentPlaceholder.fromJson(Map<String, dynamic> json) =>
AttachmentPlaceholder(
chunkCount: json['chunk_count'],
chunkSize: json['chunk_size'],
meta: Attachment.fromJson(json['meta']),
);
_$AttachmentPlaceholderFromJson(json);
Map<String, dynamic> toJson() => {
'chunk_count': chunkCount,
'chunk_size': chunkSize,
'meta': meta.toJson(),
};
Map<String, dynamic> toJson() => _$AttachmentPlaceholderToJson(this);
}
@JsonSerializable()
class Attachment {
int id;
DateTime createdAt;
@ -68,50 +65,8 @@ class Attachment {
required this.accountId,
});
factory Attachment.fromJson(Map<String, dynamic> json) => Attachment(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
rid: json['rid'],
uuid: json['uuid'],
size: json['size'],
name: json['name'],
alt: json['alt'],
mimetype: json['mimetype'],
hash: json['hash'],
destination: json['destination'],
isAnalyzed: json['is_analyzed'],
isUploaded: json['is_uploaded'],
metadata: json['metadata'],
fileChunks: json['file_chunks'],
isMature: json['is_mature'],
account:
json['account'] != null ? Account.fromJson(json['account']) : null,
accountId: json['account_id'],
);
factory Attachment.fromJson(Map<String, dynamic> json) =>
_$AttachmentFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'rid': rid,
'uuid': uuid,
'size': size,
'name': name,
'alt': alt,
'mimetype': mimetype,
'hash': hash,
'destination': destination,
'is_analyzed': isAnalyzed,
'is_uploaded': isUploaded,
'metadata': metadata,
'file_chunks': fileChunks,
'is_mature': isMature,
'account': account?.toJson(),
'account_id': accountId,
};
Map<String, dynamic> toJson() => _$AttachmentToJson(this);
}

View File

@ -0,0 +1,72 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'attachment.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AttachmentPlaceholder _$AttachmentPlaceholderFromJson(
Map<String, dynamic> json) =>
AttachmentPlaceholder(
chunkCount: (json['chunk_count'] as num).toInt(),
chunkSize: (json['chunk_size'] as num).toInt(),
meta: Attachment.fromJson(json['meta'] as Map<String, dynamic>),
);
Map<String, dynamic> _$AttachmentPlaceholderToJson(
AttachmentPlaceholder instance) =>
<String, dynamic>{
'chunk_count': instance.chunkCount,
'chunk_size': instance.chunkSize,
'meta': instance.meta.toJson(),
};
Attachment _$AttachmentFromJson(Map<String, dynamic> json) => Attachment(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
rid: json['rid'] as String,
uuid: json['uuid'] as String,
size: (json['size'] as num).toInt(),
name: json['name'] as String,
alt: json['alt'] as String,
mimetype: json['mimetype'] as String,
hash: json['hash'] as String,
destination: (json['destination'] as num).toInt(),
isAnalyzed: json['is_analyzed'] as bool,
isUploaded: json['is_uploaded'] as bool,
metadata: json['metadata'] as Map<String, dynamic>?,
fileChunks: json['file_chunks'] as Map<String, dynamic>?,
isMature: json['is_mature'] as bool,
account: json['account'] == null
? null
: Account.fromJson(json['account'] as Map<String, dynamic>),
accountId: (json['account_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$AttachmentToJson(Attachment instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'rid': instance.rid,
'uuid': instance.uuid,
'size': instance.size,
'name': instance.name,
'alt': instance.alt,
'mimetype': instance.mimetype,
'hash': instance.hash,
'destination': instance.destination,
'is_analyzed': instance.isAnalyzed,
'is_uploaded': instance.isUploaded,
'metadata': instance.metadata,
'file_chunks': instance.fileChunks,
'is_mature': instance.isMature,
'account': instance.account?.toJson(),
'account_id': instance.accountId,
};

View File

@ -1,6 +1,10 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:solian/models/channel.dart';
part 'call.g.dart';
@JsonSerializable()
class Call {
int id;
DateTime createdAt;
@ -10,7 +14,8 @@ class Call {
String externalId;
int founderId;
int channelId;
List<dynamic> participants;
@JsonKey(defaultValue: [])
List<dynamic>? participants;
Channel channel;
Call({
@ -26,32 +31,9 @@ class Call {
required this.channel,
});
factory Call.fromJson(Map<String, dynamic> json) => Call(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'],
endedAt:
json['ended_at'] != null ? DateTime.parse(json['ended_at']) : null,
externalId: json['external_id'],
founderId: json['founder_id'],
channelId: json['channel_id'],
participants: json['participants'] ?? List.empty(),
channel: Channel.fromJson(json['channel']),
);
factory Call.fromJson(Map<String, dynamic> json) => _$CallFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'ended_at': endedAt?.toIso8601String(),
'external_id': externalId,
'founder_id': founderId,
'channel_id': channelId,
'participants': participants,
'channel': channel.toJson(),
};
Map<String, dynamic> toJson() => _$CallToJson(this);
}
enum ParticipantStatsType {

37
lib/models/call.g.dart Normal file
View File

@ -0,0 +1,37 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'call.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Call _$CallFromJson(Map<String, dynamic> json) => Call(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
endedAt: json['ended_at'] == null
? null
: DateTime.parse(json['ended_at'] as String),
externalId: json['external_id'] as String,
founderId: (json['founder_id'] as num).toInt(),
channelId: (json['channel_id'] as num).toInt(),
participants: json['participants'] as List<dynamic>? ?? [],
channel: Channel.fromJson(json['channel'] as Map<String, dynamic>),
);
Map<String, dynamic> _$CallToJson(Call instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'ended_at': instance.endedAt?.toIso8601String(),
'external_id': instance.externalId,
'founder_id': instance.founderId,
'channel_id': instance.channelId,
'participants': instance.participants,
'channel': instance.channel.toJson(),
};

View File

@ -1,6 +1,10 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/realm.dart';
part 'channel.g.dart';
@JsonSerializable()
class Channel {
int id;
DateTime createdAt;
@ -17,6 +21,7 @@ class Channel {
int? realmId;
bool isEncrypted;
@JsonKey(includeFromJson: false, includeToJson: true)
bool isAvailable = false;
Channel({
@ -36,42 +41,10 @@ class Channel {
required this.realmId,
});
factory Channel.fromJson(Map<String, dynamic> json) => Channel(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'],
alias: json['alias'],
name: json['name'],
description: json['description'],
type: json['type'],
members: json['members']
?.map((e) => ChannelMember.fromJson(e))
.toList()
.cast<ChannelMember>(),
account: Account.fromJson(json['account']),
accountId: json['account_id'],
realm: json['realm'] != null ? Realm.fromJson(json['realm']) : null,
realmId: json['realm_id'],
isEncrypted: json['is_encrypted'],
);
factory Channel.fromJson(Map<String, dynamic> json) =>
_$ChannelFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'alias': alias,
'name': name,
'description': description,
'type': type,
'members': members?.map((e) => e.toJson()).toList(),
'account': account.toJson(),
'account_id': accountId,
'realm': realm?.toJson(),
'realm_id': realmId,
'is_encrypted': isEncrypted,
};
Map<String, dynamic> toJson() => _$ChannelToJson(this);
@override
bool operator ==(Object other) {
@ -83,6 +56,7 @@ class Channel {
int get hashCode => id;
}
@JsonSerializable()
class ChannelMember {
int id;
DateTime createdAt;
@ -104,25 +78,8 @@ class ChannelMember {
required this.notify,
});
factory ChannelMember.fromJson(Map<String, dynamic> json) => ChannelMember(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'],
channelId: json['channel_id'],
accountId: json['account_id'],
account: Account.fromJson(json['account']),
notify: json['notify'],
);
factory ChannelMember.fromJson(Map<String, dynamic> json) =>
_$ChannelMemberFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'channel_id': channelId,
'account_id': accountId,
'account': account.toJson(),
'notify': notify,
};
Map<String, dynamic> toJson() => _$ChannelMemberToJson(this);
}

74
lib/models/channel.g.dart Normal file
View File

@ -0,0 +1,74 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'channel.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Channel _$ChannelFromJson(Map<String, dynamic> json) => Channel(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
alias: json['alias'] as String,
name: json['name'] as String,
description: json['description'] as String,
type: (json['type'] as num).toInt(),
members: (json['members'] as List<dynamic>?)
?.map((e) => ChannelMember.fromJson(e as Map<String, dynamic>))
.toList(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
isEncrypted: json['is_encrypted'] as bool,
realm: json['realm'] == null
? null
: Realm.fromJson(json['realm'] as Map<String, dynamic>),
realmId: (json['realm_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$ChannelToJson(Channel instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'alias': instance.alias,
'name': instance.name,
'description': instance.description,
'type': instance.type,
'members': instance.members?.map((e) => e.toJson()).toList(),
'account': instance.account.toJson(),
'account_id': instance.accountId,
'realm': instance.realm?.toJson(),
'realm_id': instance.realmId,
'is_encrypted': instance.isEncrypted,
'is_available': instance.isAvailable,
};
ChannelMember _$ChannelMemberFromJson(Map<String, dynamic> json) =>
ChannelMember(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
channelId: (json['channel_id'] as num).toInt(),
accountId: (json['account_id'] as num).toInt(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
notify: (json['notify'] as num).toInt(),
);
Map<String, dynamic> _$ChannelMemberToJson(ChannelMember instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'channel_id': instance.channelId,
'account_id': instance.accountId,
'account': instance.account.toJson(),
'notify': instance.notify,
};

View File

@ -1,6 +1,9 @@
import 'package:solian/models/account.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:solian/models/channel.dart';
part 'event.g.dart';
@JsonSerializable()
class Event {
int id;
String uuid;
@ -10,10 +13,11 @@ class Event {
Map<String, dynamic> body;
String type;
Channel? channel;
Sender sender;
ChannelMember sender;
int channelId;
int senderId;
@JsonKey(includeFromJson: false, includeToJson: true)
bool isPending = false;
Event({
@ -30,38 +34,16 @@ class Event {
required this.senderId,
});
factory Event.fromJson(Map<String, dynamic> json) => Event(
id: json['id'],
uuid: json['uuid'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'],
body: json['body'],
type: json['type'],
channel:
json['channel'] != null ? Channel.fromJson(json['channel']) : null,
sender: Sender.fromJson(json['sender']),
channelId: json['channel_id'],
senderId: json['sender_id'],
);
factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'uuid': uuid,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'body': body,
'type': type,
'channel': channel?.toJson(),
'sender': sender.toJson(),
'channel_id': channelId,
'sender_id': senderId,
};
Map<String, dynamic> toJson() => _$EventToJson(this);
}
@JsonSerializable()
class EventMessageBody {
@JsonKey(defaultValue: '')
String text;
@JsonKey(defaultValue: 'plain')
String algorithm;
List<String>? attachments;
int? quoteEvent;
@ -78,69 +60,7 @@ class EventMessageBody {
});
factory EventMessageBody.fromJson(Map<String, dynamic> json) =>
EventMessageBody(
text: json['text'] ?? '',
algorithm: json['algorithm'] ?? 'plain',
attachments: json['attachments'] != null
? List<String>.from(json['attachments']?.whereType<String>())
: null,
quoteEvent: json['quote_event'],
relatedEvent: json['related_event'],
relatedUsers: json['related_users'] != null
? List<int>.from(json['related_users'].map((x) => x))
: null,
);
_$EventMessageBodyFromJson(json);
Map<String, dynamic> toJson() => {
'text': text,
'algorithm': algorithm,
'attachments': attachments?.cast<dynamic>(),
'quote_event': quoteEvent,
'related_event': relatedEvent,
'related_users': relatedUsers?.cast<dynamic>(),
};
}
class Sender {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
Account account;
int channelId;
int accountId;
int notify;
Sender({
required this.id,
required this.createdAt,
required this.updatedAt,
this.deletedAt,
required this.account,
required this.channelId,
required this.accountId,
required this.notify,
});
factory Sender.fromJson(Map<String, dynamic> json) => Sender(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'],
account: Account.fromJson(json['account']),
channelId: json['channel_id'],
accountId: json['account_id'],
notify: json['notify'],
);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'account': account.toJson(),
'channel_id': channelId,
'account_id': accountId,
'notify': notify,
};
Map<String, dynamic> toJson() => _$EventMessageBodyToJson(this);
}

64
lib/models/event.g.dart Normal file
View File

@ -0,0 +1,64 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'event.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Event _$EventFromJson(Map<String, dynamic> json) => Event(
id: (json['id'] as num).toInt(),
uuid: json['uuid'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
body: json['body'] as Map<String, dynamic>,
type: json['type'] as String,
channel: json['channel'] == null
? null
: Channel.fromJson(json['channel'] as Map<String, dynamic>),
sender: ChannelMember.fromJson(json['sender'] as Map<String, dynamic>),
channelId: (json['channel_id'] as num).toInt(),
senderId: (json['sender_id'] as num).toInt(),
);
Map<String, dynamic> _$EventToJson(Event instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'body': instance.body,
'type': instance.type,
'channel': instance.channel?.toJson(),
'sender': instance.sender.toJson(),
'channel_id': instance.channelId,
'sender_id': instance.senderId,
'is_pending': instance.isPending,
};
EventMessageBody _$EventMessageBodyFromJson(Map<String, dynamic> json) =>
EventMessageBody(
text: json['text'] as String? ?? '',
algorithm: json['algorithm'] as String? ?? 'plain',
attachments: (json['attachments'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
quoteEvent: (json['quote_event'] as num?)?.toInt(),
relatedEvent: (json['related_event'] as num?)?.toInt(),
relatedUsers: (json['related_users'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
);
Map<String, dynamic> _$EventMessageBodyToJson(EventMessageBody instance) =>
<String, dynamic>{
'text': instance.text,
'algorithm': instance.algorithm,
'attachments': instance.attachments,
'quote_event': instance.quoteEvent,
'related_event': instance.relatedEvent,
'related_users': instance.relatedUsers,
};

View File

@ -1,83 +0,0 @@
class Tag {
int id;
String alias;
String name;
String description;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
Tag({
required this.id,
required this.alias,
required this.name,
required this.description,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
});
factory Tag.fromJson(Map<String, dynamic> json) => Tag(
id: json['id'],
alias: json['alias'],
name: json['name'],
description: json['description'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
);
Map<String, dynamic> toJson() => {
'id': id,
'alias': alias,
'description': description,
'name': name,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
};
}
class Category {
int id;
String alias;
String name;
String description;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
Category({
required this.id,
required this.alias,
required this.name,
required this.description,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
});
factory Category.fromJson(Map<String, dynamic> json) => Category(
id: json['id'],
alias: json['alias'],
name: json['name'],
description: json['description'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
);
Map<String, dynamic> toJson() => {
'id': id,
'alias': alias,
'description': description,
'name': name,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
};
}

View File

@ -1,32 +0,0 @@
class Keypair {
final String id;
final String algorithm;
final String publicKey;
final String? privateKey;
final bool isOwned;
Keypair({
required this.id,
required this.algorithm,
required this.publicKey,
required this.privateKey,
this.isOwned = false,
});
factory Keypair.fromJson(Map<String, dynamic> json) => Keypair(
id: json['id'],
algorithm: json['algorithm'],
publicKey: json['public_key'],
privateKey: json['private_key'],
isOwned: json['is_owned'],
);
Map<String, dynamic> toJson() => {
'id': id,
'algorithm': algorithm,
'public_key': publicKey,
'private_key': privateKey,
'is_owned': isOwned,
};
}

View File

@ -1,3 +1,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'link.g.dart';
@JsonSerializable()
class LinkMeta {
int id;
DateTime createdAt;
@ -29,37 +34,8 @@ class LinkMeta {
required this.siteName,
});
factory LinkMeta.fromJson(Map<String, dynamic> json) => LinkMeta(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
entryId: json['entry_id'],
icon: json['icon'],
url: json['url'],
title: json['title'],
image: json['image'],
video: json['video'],
audio: json['audio'],
description: json['description'],
siteName: json['site_name'],
);
factory LinkMeta.fromJson(Map<String, dynamic> json) =>
_$LinkMetaFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'entry_id': entryId,
'icon': icon,
'url': url,
'title': title,
'image': image,
'video': video,
'audio': audio,
'description': description,
'site_name': siteName,
};
Map<String, dynamic> toJson() => _$LinkMetaToJson(this);
}

41
lib/models/link.g.dart Normal file
View File

@ -0,0 +1,41 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'link.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LinkMeta _$LinkMetaFromJson(Map<String, dynamic> json) => LinkMeta(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
entryId: json['entry_id'] as String,
icon: json['icon'] as String?,
url: json['url'] as String,
title: json['title'] as String?,
image: json['image'] as String?,
video: json['video'] as String?,
audio: json['audio'] as String?,
description: json['description'] as String?,
siteName: json['site_name'] as String?,
);
Map<String, dynamic> _$LinkMetaToJson(LinkMeta instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'entry_id': instance.entryId,
'icon': instance.icon,
'url': instance.url,
'title': instance.title,
'image': instance.image,
'video': instance.video,
'audio': instance.audio,
'description': instance.description,
'site_name': instance.siteName,
};

View File

@ -1,3 +1,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'notification.g.dart';
@JsonSerializable()
class Notification {
int id;
DateTime createdAt;
@ -25,35 +30,8 @@ class Notification {
required this.accountId,
});
factory Notification.fromJson(Map<String, dynamic> json) => Notification(
id: json['id'] ?? 0,
createdAt: json['created_at'] == null
? DateTime.now()
: DateTime.parse(json['created_at']),
updatedAt: json['updated_at'] == null
? DateTime.now()
: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'],
title: json['title'],
subtitle: json['subtitle'],
body: json['body'],
avatar: json['avatar'],
picture: json['picture'],
senderId: json['sender_id'],
accountId: json['account_id'],
);
factory Notification.fromJson(Map<String, dynamic> json) =>
_$NotificationFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'title': title,
'subtitle': subtitle,
'body': body,
'avatar': avatar,
'picture': picture,
'sender_id': senderId,
'account_id': accountId,
};
Map<String, dynamic> toJson() => _$NotificationToJson(this);
}

View File

@ -0,0 +1,38 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'notification.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Notification _$NotificationFromJson(Map<String, dynamic> json) => Notification(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
title: json['title'] as String,
subtitle: json['subtitle'] as String?,
body: json['body'] as String,
avatar: json['avatar'] as String?,
picture: json['picture'] as String?,
senderId: (json['sender_id'] as num?)?.toInt(),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$NotificationToJson(Notification instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'title': instance.title,
'subtitle': instance.subtitle,
'body': instance.body,
'avatar': instance.avatar,
'picture': instance.picture,
'sender_id': instance.senderId,
'account_id': instance.accountId,
};

View File

@ -1,7 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'packet.g.dart';
@JsonSerializable()
class NetworkPackage {
@JsonKey(name: 'w')
String method;
@JsonKey(name: 'e')
String? endpoint;
@JsonKey(name: 'm')
String? message;
@JsonKey(name: 'p')
Map<String, dynamic>? payload;
NetworkPackage({
@ -11,17 +20,8 @@ class NetworkPackage {
this.payload,
});
factory NetworkPackage.fromJson(Map<String, dynamic> json) => NetworkPackage(
method: json['w'],
endpoint: json['e'],
message: json['m'],
payload: json['p'],
);
factory NetworkPackage.fromJson(Map<String, dynamic> json) =>
_$NetworkPackageFromJson(json);
Map<String, dynamic> toJson() => {
'w': method,
'e': endpoint,
'm': message,
'p': payload,
};
Map<String, dynamic> toJson() => _$NetworkPackageToJson(this);
}

23
lib/models/packet.g.dart Normal file
View File

@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'packet.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NetworkPackage _$NetworkPackageFromJson(Map<String, dynamic> json) =>
NetworkPackage(
method: json['w'] as String,
endpoint: json['e'] as String?,
message: json['m'] as String?,
payload: json['p'] as Map<String, dynamic>?,
);
Map<String, dynamic> _$NetworkPackageToJson(NetworkPackage instance) =>
<String, dynamic>{
'w': instance.method,
'e': instance.endpoint,
'm': instance.message,
'p': instance.payload,
};

View File

@ -1,3 +1,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'pagination.g.dart';
@JsonSerializable()
class PaginationResult {
int count;
List<dynamic>? data;
@ -8,10 +13,7 @@ class PaginationResult {
});
factory PaginationResult.fromJson(Map<String, dynamic> json) =>
PaginationResult(count: json['count'], data: json['data']);
_$PaginationResultFromJson(json);
Map<String, dynamic> toJson() => {
'count': count,
'data': data,
};
Map<String, dynamic> toJson() => _$PaginationResultToJson(this);
}

View File

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'pagination.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PaginationResult _$PaginationResultFromJson(Map<String, dynamic> json) =>
PaginationResult(
count: (json['count'] as num).toInt(),
data: json['data'] as List<dynamic>?,
);
Map<String, dynamic> _$PaginationResultToJson(PaginationResult instance) =>
<String, dynamic>{
'count': instance.count,
'data': instance.data,
};

View File

@ -1,7 +1,11 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/feed.dart';
import 'package:solian/models/post_categories.dart';
import 'package:solian/models/realm.dart';
part 'post.g.dart';
@JsonSerializable()
class Post {
int id;
DateTime createdAt;
@ -57,81 +61,15 @@ class Post {
required this.metric,
});
factory Post.fromJson(Map<String, dynamic> json) => Post(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
alias: json['alias'],
areaAlias: json['area_alias'],
type: json['type'],
body: json['body'],
tags: json['tags']?.map((x) => Tag.fromJson(x)).toList().cast<Tag>(),
categories: json['categories']
?.map((x) => Category.fromJson(x))
.toList()
.cast<Category>(),
replies: json['replies'],
replyId: json['reply_id'],
repostId: json['repost_id'],
realmId: json['realm_id'],
replyTo:
json['reply_to'] != null ? Post.fromJson(json['reply_to']) : null,
repostTo:
json['repost_to'] != null ? Post.fromJson(json['repost_to']) : null,
realm: json['realm'] != null ? Realm.fromJson(json['realm']) : null,
editedAt: json['edited_at'] != null
? DateTime.parse(json['edited_at'])
: null,
publishedAt: json['published_at'] != null
? DateTime.parse(json['published_at'])
: null,
publishedUntil: json['published_until'] != null
? DateTime.parse(json['published_until'])
: null,
pinnedAt: json['pinned_at'] != null
? DateTime.parse(json['pinned_at'])
: null,
isDraft: json['is_draft'],
authorId: json['author_id'],
author: Account.fromJson(json['author']),
metric:
json['metric'] != null ? PostMetric.fromJson(json['metric']) : null,
);
factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'edited_at': editedAt?.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'alias': alias,
'area_alias': areaAlias,
'type': type,
'body': body,
'tags': tags,
'categories': categories,
'replies': replies,
'reply_id': replyId,
'repost_id': repostId,
'realm_id': realmId,
'reply_to': replyTo?.toJson(),
'repost_to': repostTo?.toJson(),
'realm': realm?.toJson(),
'published_at': publishedAt?.toIso8601String(),
'published_until': publishedUntil?.toIso8601String(),
'pinned_at': pinnedAt?.toIso8601String(),
'is_draft': isDraft,
'author_id': authorId,
'author': author.toJson(),
'metric': metric?.toJson(),
};
Map<String, dynamic> toJson() => _$PostToJson(this);
}
@JsonSerializable()
class PostMetric {
int reactionCount;
@JsonKey(defaultValue: {})
Map<String, int> reactionList;
int replyCount;
@ -141,22 +79,8 @@ class PostMetric {
required this.replyCount,
});
factory PostMetric.fromJson(Map<String, dynamic> json) => PostMetric(
reactionCount: json['reaction_count'],
replyCount: json['reply_count'],
reactionList: json['reaction_list'] != null
? json['reaction_list']
.map((key, value) => MapEntry(
key,
int.tryParse(value.toString()) ??
(value is double ? value.toInt() : null)))
.cast<String, int>()
: {},
);
factory PostMetric.fromJson(Map<String, dynamic> json) =>
_$PostMetricFromJson(json);
Map<String, dynamic> toJson() => {
'reaction_count': reactionCount,
'reply_count': replyCount,
'reaction_list': reactionList,
};
Map<String, dynamic> toJson() => _$PostMetricToJson(this);
}

103
lib/models/post.g.dart Normal file
View File

@ -0,0 +1,103 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'post.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Post _$PostFromJson(Map<String, dynamic> json) => Post(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
editedAt: json['edited_at'] == null
? null
: DateTime.parse(json['edited_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
alias: json['alias'] as String?,
areaAlias: json['area_alias'] as String?,
type: json['type'] as String,
body: json['body'],
tags: (json['tags'] as List<dynamic>?)
?.map((e) => Tag.fromJson(e as Map<String, dynamic>))
.toList(),
categories: (json['categories'] as List<dynamic>?)
?.map((e) => Category.fromJson(e as Map<String, dynamic>))
.toList(),
replies: (json['replies'] as List<dynamic>?)
?.map((e) => Post.fromJson(e as Map<String, dynamic>))
.toList(),
replyId: (json['reply_id'] as num?)?.toInt(),
repostId: (json['repost_id'] as num?)?.toInt(),
realmId: (json['realm_id'] as num?)?.toInt(),
replyTo: json['reply_to'] == null
? null
: Post.fromJson(json['reply_to'] as Map<String, dynamic>),
repostTo: json['repost_to'] == null
? null
: Post.fromJson(json['repost_to'] as Map<String, dynamic>),
realm: json['realm'] == null
? null
: Realm.fromJson(json['realm'] as Map<String, dynamic>),
publishedAt: json['published_at'] == null
? null
: DateTime.parse(json['published_at'] as String),
publishedUntil: json['published_until'] == null
? null
: DateTime.parse(json['published_until'] as String),
pinnedAt: json['pinned_at'] == null
? null
: DateTime.parse(json['pinned_at'] as String),
isDraft: json['is_draft'] as bool?,
authorId: (json['author_id'] as num).toInt(),
author: Account.fromJson(json['author'] as Map<String, dynamic>),
metric: json['metric'] == null
? null
: PostMetric.fromJson(json['metric'] as Map<String, dynamic>),
);
Map<String, dynamic> _$PostToJson(Post instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'edited_at': instance.editedAt?.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'alias': instance.alias,
'area_alias': instance.areaAlias,
'body': instance.body,
'tags': instance.tags?.map((e) => e.toJson()).toList(),
'categories': instance.categories?.map((e) => e.toJson()).toList(),
'replies': instance.replies?.map((e) => e.toJson()).toList(),
'type': instance.type,
'reply_id': instance.replyId,
'repost_id': instance.repostId,
'realm_id': instance.realmId,
'reply_to': instance.replyTo?.toJson(),
'repost_to': instance.repostTo?.toJson(),
'realm': instance.realm?.toJson(),
'published_at': instance.publishedAt?.toIso8601String(),
'published_until': instance.publishedUntil?.toIso8601String(),
'pinned_at': instance.pinnedAt?.toIso8601String(),
'is_draft': instance.isDraft,
'author_id': instance.authorId,
'author': instance.author.toJson(),
'metric': instance.metric?.toJson(),
};
PostMetric _$PostMetricFromJson(Map<String, dynamic> json) => PostMetric(
reactionCount: (json['reaction_count'] as num).toInt(),
reactionList: (json['reaction_list'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toInt()),
) ??
{},
replyCount: (json['reply_count'] as num).toInt(),
);
Map<String, dynamic> _$PostMetricToJson(PostMetric instance) =>
<String, dynamic>{
'reaction_count': instance.reactionCount,
'reaction_list': instance.reactionList,
'reply_count': instance.replyCount,
};

View File

@ -0,0 +1,54 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'post_categories.g.dart';
@JsonSerializable()
class Tag {
int id;
String alias;
String name;
String description;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
Tag({
required this.id,
required this.alias,
required this.name,
required this.description,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
});
factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
Map<String, dynamic> toJson() => _$TagToJson(this);
}
@JsonSerializable()
class Category {
int id;
String alias;
String name;
String description;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
Category({
required this.id,
required this.alias,
required this.name,
required this.description,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
});
factory Category.fromJson(Map<String, dynamic> json) =>
_$CategoryFromJson(json);
Map<String, dynamic> toJson() => _$CategoryToJson(this);
}

View File

@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'post_categories.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Tag _$TagFromJson(Map<String, dynamic> json) => Tag(
id: (json['id'] as num).toInt(),
alias: json['alias'] as String,
name: json['name'] as String,
description: json['description'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$TagToJson(Tag instance) => <String, dynamic>{
'id': instance.id,
'alias': instance.alias,
'name': instance.name,
'description': instance.description,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
Category _$CategoryFromJson(Map<String, dynamic> json) => Category(
id: (json['id'] as num).toInt(),
alias: json['alias'] as String,
name: json['name'] as String,
description: json['description'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$CategoryToJson(Category instance) => <String, dynamic>{
'id': instance.id,
'alias': instance.alias,
'name': instance.name,
'description': instance.description,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};

View File

@ -1,5 +1,9 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:solian/models/account.dart';
part 'realm.g.dart';
@JsonSerializable()
class Realm {
int id;
DateTime createdAt;
@ -25,33 +29,9 @@ class Realm {
this.accountId,
});
factory Realm.fromJson(Map<String, dynamic> json) => Realm(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
alias: json['alias'],
name: json['name'],
description: json['description'],
isPublic: json['is_public'],
isCommunity: json['is_community'],
accountId: json['account_id'],
);
factory Realm.fromJson(Map<String, dynamic> json) => _$RealmFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'alias': alias,
'name': name,
'description': description,
'is_public': isPublic,
'is_community': isCommunity,
'account_id': accountId,
};
Map<String, dynamic> toJson() => _$RealmToJson(this);
@override
bool operator ==(Object other) {
@ -65,6 +45,7 @@ class Realm {
int get hashCode => id;
}
@JsonSerializable()
class RealmMember {
int id;
DateTime createdAt;
@ -86,27 +67,8 @@ class RealmMember {
required this.powerLevel,
});
factory RealmMember.fromJson(Map<String, dynamic> json) => RealmMember(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
realmId: json['realm_id'],
accountId: json['account_id'],
account: Account.fromJson(json['account']),
powerLevel: json['power_level'],
);
factory RealmMember.fromJson(Map<String, dynamic> json) =>
_$RealmMemberFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'realm_id': realmId,
'account_id': accountId,
'account': account.toJson(),
'power_level': powerLevel,
};
Map<String, dynamic> toJson() => _$RealmMemberToJson(this);
}

60
lib/models/realm.g.dart Normal file
View File

@ -0,0 +1,60 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'realm.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Realm _$RealmFromJson(Map<String, dynamic> json) => Realm(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
alias: json['alias'] as String,
name: json['name'] as String,
description: json['description'] as String,
isPublic: json['is_public'] as bool,
isCommunity: json['is_community'] as bool,
accountId: (json['account_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$RealmToJson(Realm instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'alias': instance.alias,
'name': instance.name,
'description': instance.description,
'is_public': instance.isPublic,
'is_community': instance.isCommunity,
'account_id': instance.accountId,
};
RealmMember _$RealmMemberFromJson(Map<String, dynamic> json) => RealmMember(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
realmId: (json['realm_id'] as num).toInt(),
accountId: (json['account_id'] as num).toInt(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
powerLevel: (json['power_level'] as num).toInt(),
);
Map<String, dynamic> _$RealmMemberToJson(RealmMember instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'realm_id': instance.realmId,
'account_id': instance.accountId,
'account': instance.account.toJson(),
'power_level': instance.powerLevel,
};

View File

@ -1,5 +1,9 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:solian/models/account.dart';
part 'relations.g.dart';
@JsonSerializable()
class Relationship {
int id;
DateTime createdAt;
@ -23,27 +27,8 @@ class Relationship {
required this.status,
});
factory Relationship.fromJson(Map<String, dynamic> json) => Relationship(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'],
accountId: json['account_id'],
relatedId: json['related_id'],
account: Account.fromJson(json['account']),
related: Account.fromJson(json['related']),
status: json['status'],
);
factory Relationship.fromJson(Map<String, dynamic> json) =>
_$RelationshipFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt,
'account_id': accountId,
'related_id': relatedId,
'account': account.toJson(),
'related': related.toJson(),
'status': status,
};
Map<String, dynamic> toJson() => _$RelationshipToJson(this);
}

View File

@ -0,0 +1,34 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'relations.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Relationship _$RelationshipFromJson(Map<String, dynamic> json) => Relationship(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
accountId: (json['account_id'] as num).toInt(),
relatedId: (json['related_id'] as num).toInt(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
related: Account.fromJson(json['related'] as Map<String, dynamic>),
status: (json['status'] as num).toInt(),
);
Map<String, dynamic> _$RelationshipToJson(Relationship instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'account_id': instance.accountId,
'related_id': instance.relatedId,
'account': instance.account.toJson(),
'related': instance.related.toJson(),
'status': instance.status,
};

View File

@ -1,7 +1,11 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/services.dart';
part 'stickers.g.dart';
@JsonSerializable()
class Sticker {
int id;
DateTime createdAt;
@ -39,38 +43,13 @@ class Sticker {
'/attachments/${attachment.rid}',
);
factory Sticker.fromJson(Map<String, dynamic> json) => Sticker(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: json['deleted_at'],
alias: json['alias'],
name: json['name'],
attachmentId: json['attachment_id'],
attachment: Attachment.fromJson(json['attachment']),
packId: json['pack_id'],
pack: json['pack'] != null ? StickerPack.fromJson(json['pack']) : null,
accountId: json['account_id'],
account: Account.fromJson(json['account']),
);
factory Sticker.fromJson(Map<String, dynamic> json) =>
_$StickerFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'alias': alias,
'name': name,
'attachment_id': attachmentId,
'attachment': attachment.toJson(),
'pack_id': packId,
'account_id': accountId,
'account': account.toJson(),
};
Map<String, dynamic> toJson() => _$StickerToJson(this);
}
@JsonSerializable()
class StickerPack {
int id;
DateTime createdAt;
@ -96,36 +75,8 @@ class StickerPack {
required this.account,
});
factory StickerPack.fromJson(Map<String, dynamic> json) => StickerPack(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: json['deleted_at'],
prefix: json['prefix'],
name: json['name'],
description: json['description'],
stickers: json['stickers'] == null
? []
: List<Sticker>.from(
json['stickers']!.map((x) => Sticker.fromJson(x))),
accountId: json['account_id'],
account: Account.fromJson(json['account']),
);
factory StickerPack.fromJson(Map<String, dynamic> json) =>
_$StickerPackFromJson(json);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'prefix': prefix,
'name': name,
'description': description,
'stickers': stickers == null
? []
: List<dynamic>.from(stickers!.map((x) => x.toJson())),
'account_id': accountId,
'account': account.toJson(),
};
Map<String, dynamic> toJson() => _$StickerPackToJson(this);
}

View File

@ -0,0 +1,73 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'stickers.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Sticker _$StickerFromJson(Map<String, dynamic> json) => Sticker(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
alias: json['alias'] as String,
name: json['name'] as String,
attachmentId: (json['attachment_id'] as num).toInt(),
attachment:
Attachment.fromJson(json['attachment'] as Map<String, dynamic>),
packId: (json['pack_id'] as num).toInt(),
pack: json['pack'] == null
? null
: StickerPack.fromJson(json['pack'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
);
Map<String, dynamic> _$StickerToJson(Sticker instance) => <String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'alias': instance.alias,
'name': instance.name,
'attachment_id': instance.attachmentId,
'attachment': instance.attachment.toJson(),
'pack_id': instance.packId,
'pack': instance.pack?.toJson(),
'account_id': instance.accountId,
'account': instance.account.toJson(),
};
StickerPack _$StickerPackFromJson(Map<String, dynamic> json) => StickerPack(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
prefix: json['prefix'] as String,
name: json['name'] as String,
description: json['description'] as String,
stickers: (json['stickers'] as List<dynamic>?)
?.map((e) => Sticker.fromJson(e as Map<String, dynamic>))
.toList(),
accountId: (json['account_id'] as num).toInt(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
);
Map<String, dynamic> _$StickerPackToJson(StickerPack instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'prefix': instance.prefix,
'name': instance.name,
'description': instance.description,
'stickers': instance.stickers?.map((e) => e.toJson()).toList(),
'account_id': instance.accountId,
'account': instance.account.toJson(),
};

View File

@ -2,9 +2,30 @@ import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/daily_sign.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/providers/auth.dart';
class DailySignProvider extends GetxController {
Future<List<DailySignRecord>> listLastRecord(int take) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('id');
final resp = await client.get('/daily?take=$take');
if (resp.statusCode != 200 && resp.statusCode != 404) {
throw RequestException(resp);
} else if (resp.statusCode == 404) {
return List.empty();
}
final result = PaginationResult.fromJson(resp.body);
return List.from(
result.data?.map((x) => DailySignRecord.fromJson(x)) ?? [],
);
}
Future<DailySignRecord?> getToday() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();

View File

@ -0,0 +1,26 @@
extension DurationToHumanReadableString on Duration {
String toHumanReadableString({padZero = true}) {
final mm = inMinutes
.remainder(60)
.toString()
.padLeft(2, !padZero && inHours == 0 ? '' : '0');
final ss = inSeconds.remainder(60).toString().padLeft(2, '0');
if (inHours > 0) {
final hh = inHours.toString().padLeft(2, !padZero ? '' : '0');
return '$hh:$mm:$ss';
}
return '$mm:$ss';
}
}
extension ParseDuration on Duration {
static Duration fromString(String duration) {
final parts = duration.split(':').reversed.toList();
final seconds = int.parse(parts[0]);
final minutes = parts.length > 1 ? int.parse(parts[1]) : 0;
final hours = parts.length > 2 ? int.parse(parts[2]) : 0;
return Duration(hours: hours, minutes: minutes, seconds: seconds);
}
}

View File

@ -0,0 +1,51 @@
import 'package:get/get.dart';
import 'package:intl/intl.dart';
class ExperienceProvider extends GetxController {
static List<int> experienceToLevelRequirements = [
0, // Level 0
100, // Level 1
400, // Level 2
900, // Level 3
1600, // Level 4
2500, // Level 5
3600, // Level 6
4900, // Level 7
6400, // Level 8
8100, // Level 9
10000, // Level 10
12100, // Level 11
14400, // Level 12
36800 // Level 13
];
static List<String> levelLabelMapping =
List.generate(experienceToLevelRequirements.length, (x) => 'userLevel$x');
static (int level, String label) getLevelFromExp(int experience) {
final exp = experienceToLevelRequirements.reversed
.firstWhere((x) => x <= experience);
final idx = experienceToLevelRequirements.indexOf(exp);
return (idx, levelLabelMapping[idx]);
}
static double calcLevelUpProgress(int experience) {
final exp = experienceToLevelRequirements.reversed
.firstWhere((x) => x <= experience);
final idx = experienceToLevelRequirements.indexOf(exp);
if (idx + 1 >= experienceToLevelRequirements.length) return 1;
final nextExp = experienceToLevelRequirements[idx + 1];
return exp / nextExp;
}
static String calcLevelUpProgressLevel(int experience) {
final exp = experienceToLevelRequirements.reversed
.firstWhere((x) => x <= experience);
final idx = experienceToLevelRequirements.indexOf(exp);
if (idx + 1 >= experienceToLevelRequirements.length) return 'Infinity';
final nextExp = experienceToLevelRequirements[idx + 1];
final formatter =
NumberFormat.compactCurrency(symbol: '', decimalDigits: 1);
return '${formatter.format(exp)}/${formatter.format(nextExp)}';
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -21,7 +22,7 @@ class AboutScreen extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: Image.asset('assets/logo.png', width: 120, height: 120),
),
const SizedBox(height: 8),
const Gap(8),
Text(
'Solian',
style: Theme.of(context).textTheme.headlineMedium,
@ -30,12 +31,12 @@ class AboutScreen extends StatelessWidget {
'The Solar Network',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(height: 8),
const Gap(8),
FutureBuilder(
future: PackageInfo.fromPlatform(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
return const SizedBox.shrink();
}
return Text(
@ -45,7 +46,7 @@ class AboutScreen extends StatelessWidget {
},
),
Text('Copyright © ${DateTime.now().year} Solsynth LLC'),
const SizedBox(height: 16),
const Gap(16),
TextButton(
style: denseButtonStyle,
child: const Text('App Details'),
@ -59,7 +60,8 @@ class AboutScreen extends StatelessWidget {
'The Solar Network App is an intuitive and self-hostable social network and computing platform. Experience the freedom of a user-friendly design that empowers you to create and connect with communities on your own terms. Embrace the future of social networking with a platform that prioritizes your independence and privacy.',
applicationIcon: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: Image.asset('assets/logo.png', width: 60, height: 60),
child:
Image.asset('assets/logo.png', width: 60, height: 60),
),
);
},
@ -71,7 +73,7 @@ class AboutScreen extends StatelessWidget {
launchUrlString('https://solsynth.dev/products/solar-network');
},
),
const SizedBox(height: 16),
const Gap(16),
const Text(
'Open-sourced under AGPLv3',
style: TextStyle(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/relations.dart';
@ -56,7 +57,7 @@ class _FriendScreenState extends State<FriendScreen>
mainAxisSize: MainAxisSize.min,
children: [
Text('accountFriendNewHint'.tr, textAlign: TextAlign.left),
const SizedBox(height: 18),
const Gap(18),
TextField(
controller: controller,
decoration: InputDecoration(

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
@ -185,7 +186,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
child: ListView(
children: [
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
const SizedBox(height: 24),
const Gap(24),
Stack(
children: [
AccountAvatar(content: _avatar, radius: 40),
@ -202,7 +203,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
],
).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16),
const Gap(16),
Stack(
children: [
ClipRRect(
@ -247,7 +248,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
],
).paddingSymmetric(horizontal: padding),
const SizedBox(height: 24),
const Gap(24),
Row(
children: [
Flexible(
@ -262,7 +263,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
),
),
const SizedBox(width: 16),
const Gap(16),
Flexible(
flex: 1,
child: TextField(
@ -275,7 +276,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
],
).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16),
const Gap(16),
Row(
children: [
Flexible(
@ -288,7 +289,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
),
),
const SizedBox(width: 16),
const Gap(16),
Flexible(
flex: 1,
child: TextField(
@ -301,7 +302,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
],
).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16),
const Gap(16),
TextField(
controller: _descriptionController,
keyboardType: TextInputType.multiline,
@ -312,7 +313,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
labelText: 'description'.tr,
),
).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16),
const Gap(16),
TextField(
controller: _birthdayController,
readOnly: true,
@ -322,7 +323,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
onTap: () => _selectBirthday(),
).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16),
const Gap(16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/controllers/post_list_controller.dart';
@ -7,10 +8,12 @@ import 'package:solian/models/account.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/account_status.dart';
import 'package:solian/providers/relation.dart';
import 'package:solian/services.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_heading.dart';
import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/attachments/attachment_list.dart';
import 'package:solian/widgets/posts/post_list.dart';
@ -83,10 +86,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
}
int get _userSocialCreditPoints {
int birthPart =
DateTime.now().difference(_userinfo!.createdAt.toLocal()).inSeconds;
birthPart = birthPart >> 16;
return _totalUpvote * 2 - _totalDownvote + birthPart;
return _totalUpvote * 2 - _totalDownvote + _postController.postTotal.value;
}
@override
@ -96,8 +96,9 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
_postController = PostListController(author: widget.name);
_albumPagingController.addPageRequestListener((pageKey) async {
final client = ServiceFinder.configureClient('files');
final resp = await client
.get('/attachments?take=10&offset=$pageKey&author=${widget.name}');
final resp = await client.get(
'/attachments?take=10&offset=$pageKey&author=${widget.name}&original=true',
);
if (resp.statusCode == 200) {
final result = PaginationResult.fromJson(resp.body);
final out = result.data
@ -144,7 +145,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
return Material(
color: Theme.of(context).colorScheme.surface,
child: DefaultTabController(
length: 2,
length: 3,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
@ -156,12 +157,11 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
automaticallyImplyLeading: false,
flexibleSpace: Row(
children: [
AppBarLeadingButton.adaptive(context) ??
const SizedBox(width: 8),
const SizedBox(width: 8),
AppBarLeadingButton.adaptive(context) ?? const Gap(8),
const Gap(8),
if (_userinfo != null)
AccountAvatar(content: _userinfo!.avatar, radius: 16),
const SizedBox(width: 12),
const Gap(12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -213,6 +213,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
),
bottom: TabBar(
tabs: [
Tab(text: 'profilePage'.tr),
Tab(text: 'profilePosts'.tr),
Tab(text: 'profileAlbum'.tr),
],
@ -223,6 +224,24 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
body: TabBarView(
physics: const NeverScrollableScrollPhysics(),
children: [
Column(
children: [
const Gap(16),
AccountHeadingWidget(
name: _userinfo!.name,
nick: _userinfo!.nick,
desc: _userinfo!.description,
badges: _userinfo!.badges,
banner: _userinfo!.banner,
avatar: _userinfo!.avatar,
status: Get.find<StatusProvider>()
.getSomeoneStatus(_userinfo!.name),
detail: _userinfo,
profile: _userinfo!.profile,
extraWidgets: const [],
),
],
),
RefreshIndicator(
onRefresh: () => Future.wait([
_postController.reloadAllOver(),
@ -243,7 +262,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
),
],
),
const SizedBox(height: 16),
const Gap(16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [

View File

@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/pagination.dart';
@ -163,7 +164,7 @@ class _StickerScreenState extends State<StickerScreen> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(item.name),
const SizedBox(width: 6),
const Gap(6),
Badge(
label: Text('#${item.id}'),
)

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:solian/exts.dart';
@ -160,7 +161,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 12),
const Gap(12),
TextField(
obscureText: true,
autocorrect: false,
@ -176,7 +177,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => performAction(),
),
const SizedBox(height: 12),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/services.dart';
@ -94,7 +95,7 @@ class _SignUpPopupState extends State<SignUpPopup> {
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 12),
const Gap(12),
TextField(
autocorrect: false,
enableSuggestions: false,
@ -108,7 +109,7 @@ class _SignUpPopupState extends State<SignUpPopup> {
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 12),
const Gap(12),
TextField(
autocorrect: false,
enableSuggestions: false,
@ -122,7 +123,7 @@ class _SignUpPopupState extends State<SignUpPopup> {
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 12),
const Gap(12),
TextField(
obscureText: true,
autocorrect: false,
@ -138,7 +139,7 @@ class _SignUpPopupState extends State<SignUpPopup> {
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => performAction(context),
),
const SizedBox(height: 16),
const Gap(16),
Align(
alignment: Alignment.centerRight,
child: TextButton(

View File

@ -3,6 +3,7 @@ import 'dart:math' as math;
import 'package:async/async.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/providers/call.dart';
import 'package:solian/theme.dart';
@ -83,7 +84,7 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
participant: call.focusTrack.value!,
onTap: () {},
)
: const SizedBox(),
: const SizedBox.shrink(),
),
Positioned(
left: 0,
@ -257,7 +258,7 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 6),
const Gap(6),
Text(call.lastDuration.value)
],
);
@ -276,7 +277,7 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
'callStatusReconnecting'.tr,
}[call.room.connectionState]!,
),
const SizedBox(width: 6),
const Gap(6),
if (connectionQuality !=
livekit.ConnectionQuality.unknown)
Icon(

View File

@ -98,6 +98,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
setState(() => _ongoingCall = Call.fromJson(resp.body));
}
} catch (e) {
print((e as dynamic).stackTrace);
context.showErrorDialog(e);
}
@ -222,7 +223,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
actions: [
const BackgroundStateWidget(),
Builder(builder: (context) {
if (_isBusy || _channel == null) return const SizedBox();
if (_isBusy || _channel == null) return const SizedBox.shrink();
return ChatCallButton(
realm: _channel!.realm,
@ -299,7 +300,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
if (_chatController.isLoading.isTrue) {
return const LinearProgressIndicator().animate().slideY();
} else {
return const SizedBox();
return const SizedBox.shrink();
}
}),
ClipRect(
@ -350,7 +351,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
]),
);
}
return const SizedBox();
return const SizedBox.shrink();
}),
],
);

View File

@ -1,6 +1,7 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/channel.dart';
@ -148,7 +149,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
size: 18,
),
),
const SizedBox(width: 16),
const Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -112,7 +112,7 @@ class _ChatScreenState extends State<ChatScreen> {
children: [
Obx(() {
if (_channels.isLoading.isFalse) {
return const SizedBox();
return const SizedBox.shrink();
} else {
return const LinearProgressIndicator();
}

View File

@ -23,7 +23,9 @@ import 'package:solian/providers/message/adaptor.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/chat/chat_event.dart';
import 'package:solian/widgets/daily_sign/history_chart.dart';
import 'package:solian/widgets/posts/post_list.dart';
class DashboardScreen extends StatefulWidget {
@ -51,7 +53,6 @@ class _DashboardScreenState extends State<DashboardScreen> {
int? _currentPostsCount;
Future<void> _pullPosts() async {
print(_lastRead.feedLastReadAt);
if (_lastRead.feedLastReadAt == null) return;
log('[Dashboard] Pulling posts with pivot: ${_lastRead.feedLastReadAt}');
final resp = await _posts.seeWhatsNew(_lastRead.feedLastReadAt!);
@ -81,10 +82,14 @@ class _DashboardScreenState extends State<DashboardScreen> {
bool _signingDaily = true;
DailySignRecord? _signRecord;
List<DailySignRecord>? _signRecordHistory;
Future<void> _pullDaily() async {
try {
_signRecord = await _dailySign.getToday();
_dailySign.listLastRecord(30).then((value) {
setState(() => _signRecordHistory = value);
});
} catch (e) {
context.showErrorDialog(e);
}
@ -131,68 +136,88 @@ class _DashboardScreenState extends State<DashboardScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'today'.tr,
DateTime.now().day == DateTime.now().toUtc().day
? 'today'.tr
: 'yesterday'.tr,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(DateFormat('yyyy/MM/dd').format(DateTime.now())),
Text(DateFormat('yyyy/MM/dd').format(DateTime.now().toUtc())),
],
).paddingOnly(top: 8, left: 18, right: 18, bottom: 12),
Card(
child: ListTile(
leading: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('dd').format(DateTime.now()),
style: GoogleFonts.robotoMono(
fontSize: 22, height: 1.2),
child: Column(
children: [
ListTile(
leading: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('dd').format(DateTime.now()),
style: GoogleFonts.robotoMono(
fontSize: 22, height: 1.2),
),
Text(
DateFormat('yy/MM').format(DateTime.now()),
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
)
: Text(
_signRecord!.symbol,
style: GoogleFonts.notoSerifHk(
fontSize: 20, height: 1),
).paddingSymmetric(horizontal: 9),
).paddingOnly(left: 4),
title: _signRecord == null
? Text('dailySign'.tr)
: Text(_signRecord!.overviewSuggestion),
subtitle: _signRecord == null
? Text('dailySignNone'.tr)
: Text('+${_signRecord!.resultExperience} EXP'),
trailing: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? IconButton(
tooltip: '上香求签',
icon: const Icon(Icons.local_fire_department),
onPressed: _signingDaily ? null : _signDaily,
)
: IconButton(
tooltip: '查看运势历史',
icon: const Icon(Icons.history),
onPressed: () {
showDialog(
context: context,
useRootNavigator: true,
builder: (context) =>
DailySignHistoryChartDialog(
data: _signRecordHistory,
),
);
},
),
Text(
DateFormat('yy/MM').format(DateTime.now()),
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
)
: Text(
_signRecord!.symbol,
style: GoogleFonts.notoSerifHk(fontSize: 20, height: 1),
).paddingSymmetric(horizontal: 9),
).paddingOnly(left: 4),
title: _signRecord == null
? Text('dailySign'.tr)
: Text(_signRecord!.overviewSuggestion),
subtitle: _signRecord == null
? Text('dailySignNone'.tr)
: Text('+${_signRecord!.resultExperience} EXP'),
trailing: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? IconButton(
tooltip: '上香求签',
icon: const Icon(Icons.local_fire_department),
onPressed: _signingDaily ? null : _signDaily,
)
: const SizedBox(),
),
),
),
],
),
).paddingSymmetric(horizontal: 8),
const Divider(thickness: 0.3).paddingSymmetric(vertical: 8),
@ -469,7 +494,9 @@ class _DashboardScreenState extends State<DashboardScreen> {
/// Footer
Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: SolianTheme.isLargeScreen(context)
? MainAxisAlignment.start
: MainAxisAlignment.center,
children: [
Text(
'Powered by Solar Network',
@ -477,16 +504,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
),
Text(
'dashboardFooter'.tr,
style: [const Locale('zh', 'CN'), const Locale('zh', 'HK')]
.contains(Get.deviceLocale)
? GoogleFonts.notoSerifHk(
color: _unFocusColor,
fontSize: 12,
)
: TextStyle(
color: _unFocusColor,
fontSize: 12,
),
style: TextStyle(color: _unFocusColor, fontSize: 12),
)
],
).paddingAll(8),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/post_list_controller.dart';
import 'package:solian/providers/auth.dart';
@ -154,7 +155,7 @@ class PostCreatePopup extends StatelessWidget {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) {
return const SizedBox();
return const SizedBox.shrink();
}
final List<dynamic> actionList = [
@ -224,7 +225,7 @@ class PostCreatePopup extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
x.icon,
const SizedBox(height: 8),
const Gap(8),
Expanded(
child: Text(
x.label,

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:markdown_toolbar/markdown_toolbar.dart';
@ -183,7 +184,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 6),
const Gap(6),
if (_editorController.aliasController.text.isNotEmpty)
Badge(
label: Text('#${_editorController.aliasController.text}'),
@ -296,7 +297,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
?.unfocus(),
),
),
const SizedBox(height: 120)
const Gap(120)
],
),
),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/models/realm.dart';
import 'package:solian/providers/auth.dart';
@ -95,7 +96,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
backgroundColor: Colors.teal,
child: Icon(Icons.group, color: Colors.white),
),
const SizedBox(width: 16),
const Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/current_state_action.dart';
class TitleShell extends StatelessWidget {
final bool showAppBar;
@ -32,6 +33,12 @@ class TitleShell extends StatelessWidget {
),
centerTitle: isCenteredTitle,
toolbarHeight: SolianTheme.toolbarHeight(context),
actions: [
const BackgroundStateWidget(),
SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
),
],
)
: null,
body: child,

View File

@ -10,6 +10,7 @@ const i18nEnglish = {
'draft': 'Draft',
'dashboard': 'Dashboard',
'today': 'Today',
'yesterday': 'Yesterday',
'draftSave': 'Save',
'draftBox': 'Draft Box',
'more': 'More',
@ -33,6 +34,7 @@ const i18nEnglish = {
'dailySignTier4': 'Everything will be awesome',
'dashboardFooter': 'Don\'t be serious, just for fun.',
'visitProfilePage': 'Visit Profile Page',
'profilePage': 'Page',
'profilePosts': 'Posts',
'profileAlbum': 'Album',
'chat': 'Chat',
@ -400,4 +402,18 @@ const i18nEnglish = {
'collapse': 'Collapse',
'expand': 'Expand',
'typingMessage': '@user are typing...',
'userLevel0': 'Newbie',
'userLevel1': 'Novice',
'userLevel2': 'Apprentice',
'userLevel3': 'Explorer',
'userLevel4': 'Adventurer',
'userLevel5': 'Warrior',
'userLevel6': 'Knight',
'userLevel7': 'Champion',
'userLevel8': 'Hero',
'userLevel9': 'Master',
'userLevel10': 'Grandmaster',
'userLevel11': 'Legend',
'userLevel12': 'Mythic',
'userLevel13': 'Immortal',
};

View File

@ -26,6 +26,7 @@ const i18nSimplifiedChinese = {
'unlink': '移除链接',
'dashboard': '仪表盘',
'today': '今日',
'yesterday': '昨日',
'feedSearch': '搜索资讯',
'feedSearchWithTag': '检索带有 #@key 标签的资讯',
'feedSearchWithCategory': '检索位于分类 @category 的资讯',
@ -41,6 +42,7 @@ const i18nSimplifiedChinese = {
'dailySignTier4': '诸事皆宜',
'dashboardFooter': '占卜多少沾点玩,人生还得靠实力',
'visitProfilePage': '造访个人主页',
'profilePage': '主页',
'profilePosts': '帖子',
'profileAlbum': '相簿',
'chat': '聊天',
@ -370,4 +372,18 @@ const i18nSimplifiedChinese = {
'collapse': '折叠',
'expand': '展开',
'typingMessage': '@user 正在输入中…',
'userLevel0': '不慕名利',
'userLevel1': '初出茅庐',
'userLevel2': '小试牛刀',
'userLevel3': '磨杵成针',
'userLevel4': '披荆斩棘',
'userLevel5': '力挽狂澜',
'userLevel6': '一骑当千',
'userLevel7': '所向披靡',
'userLevel8': '气吞山河',
'userLevel9': '登峰造极',
'userLevel10': '出神入化',
'userLevel11': '名垂千古',
'userLevel12': '独占鳌头',
'userLevel13': '万古流芳',
};

View File

@ -45,7 +45,7 @@ class _AccountBadgeWidgetState extends State<AccountBadgeWidget> {
Widget build(BuildContext context) {
final spec = badges[widget.item.type];
if (spec == null) return const SizedBox();
if (spec == null) return const SizedBox.shrink();
return Tooltip(
richMessage: TextSpan(

View File

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/account_status.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/account_status.dart';
import 'package:solian/providers/experience.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_badge.dart';
import 'package:solian/widgets/account/account_status_action.dart';
@ -17,6 +20,7 @@ class AccountHeadingWidget extends StatelessWidget {
final String nick;
final String? desc;
final Account? detail;
final AccountProfile? profile;
final List<AccountBadge>? badges;
final List<Widget>? extraWidgets;
@ -32,6 +36,7 @@ class AccountHeadingWidget extends StatelessWidget {
required this.desc,
required this.badges,
this.detail,
this.profile,
this.status,
this.extraWidgets,
this.onEditStatus,
@ -70,7 +75,7 @@ class AccountHeadingWidget extends StatelessWidget {
content: banner,
fit: BoxFit.cover,
)
: const SizedBox(),
: const SizedBox.shrink(),
),
),
).paddingSymmetric(horizontal: 16),
@ -129,7 +134,10 @@ class AccountHeadingWidget extends StatelessWidget {
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(info.$3),
Text(
info.$3,
style: const TextStyle(height: 1),
).paddingOnly(bottom: 3),
if (!status.isOnline && status.lastSeenAt != null)
Opacity(
opacity: 0.75,
@ -150,7 +158,7 @@ class AccountHeadingWidget extends StatelessWidget {
),
],
).paddingOnly(left: 116, top: 6),
const SizedBox(height: 4),
const Gap(4),
if (badges?.isNotEmpty ?? false)
SizedBox(
width: double.infinity,
@ -181,6 +189,63 @@ class AccountHeadingWidget extends StatelessWidget {
),
),
).paddingSymmetric(horizontal: 16),
if (profile != null)
Card(
child: ListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
visualDensity:
const VisualDensity(horizontal: -4, vertical: -2),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
ExperienceProvider.getLevelFromExp(
profile!.experience ?? 0,
).$2.tr,
),
const Gap(4),
Badge(
label: Text(
'Lv${ExperienceProvider.getLevelFromExp(
profile!.experience ?? 0,
).$1}',
style: GoogleFonts.dosis(
fontWeight: FontWeight.bold,
),
),
).paddingOnly(top: 1),
],
),
subtitle: SizedBox(
height: 20,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: LinearProgressIndicator(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
value: ExperienceProvider.calcLevelUpProgress(
profile!.experience ?? 0,
),
),
),
const Gap(8),
Text(
'${ExperienceProvider.calcLevelUpProgressLevel(profile!.experience ?? 0)} EXP',
style: GoogleFonts.robotoMono(
fontSize: 10,
height: 0.5,
),
),
],
),
),
),
).paddingSymmetric(horizontal: 16),
SizedBox(
width: double.infinity,
child: Card(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/models/account.dart';
import 'package:solian/providers/account_status.dart';
@ -72,7 +73,7 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.cancel, size: 24),
const SizedBox(height: 12),
const Gap(12),
Text(
_hasError.toString(),
textAlign: TextAlign.center,
@ -98,6 +99,7 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
nick: _userinfo!.nick,
desc: _userinfo!.description,
detail: _userinfo!,
profile: _userinfo!.profile,
badges: _userinfo!.badges,
status:
Get.find<StatusProvider>().getSomeoneStatus(_userinfo!.name),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/exts.dart';
@ -214,7 +215,7 @@ class _AccountStatusEditorDialogState extends State<AccountStatusEditorDialog> {
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: const LinearProgressIndicator().animate().scaleX(),
),
const SizedBox(height: 18),
const Gap(18),
TextField(
controller: _labelController,
decoration: InputDecoration(
@ -226,7 +227,7 @@ class _AccountStatusEditorDialogState extends State<AccountStatusEditorDialog> {
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 8),
const Gap(8),
TextField(
controller: _clearAtController,
readOnly: true,
@ -238,7 +239,7 @@ class _AccountStatusEditorDialogState extends State<AccountStatusEditorDialog> {
),
onTap: () => selectClearAt(),
),
const SizedBox(height: 8),
const Gap(8),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(
@ -281,7 +282,7 @@ class _AccountStatusEditorDialogState extends State<AccountStatusEditorDialog> {
],
),
),
const SizedBox(height: 8),
const Gap(8),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/screens/auth/signin.dart';
@ -20,7 +21,7 @@ class SigninRequiredOverlay extends StatelessWidget {
Icons.login,
size: 48,
),
const SizedBox(height: 8),
const Gap(8),
Text(
'signinRequired'.tr,
style: Theme.of(context).textTheme.titleLarge,

View File

@ -22,7 +22,7 @@ class AppBarLeadingButton extends StatelessWidget {
onPressed: () => rootScaffoldKey.currentState!.openDrawer(),
);
} else {
return const SizedBox();
return const SizedBox.shrink();
}
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/attachment.dart';
@ -74,7 +75,7 @@ class _AttachmentAttrEditorDialogState
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: const LinearProgressIndicator().animate().scaleX(),
),
const SizedBox(height: 18),
const Gap(18),
TextField(
controller: _altController,
decoration: InputDecoration(
@ -86,7 +87,7 @@ class _AttachmentAttrEditorDialogState
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 8),
const Gap(8),
CheckboxListTile(
contentPadding: const EdgeInsets.only(left: 4, right: 18),
shape: const RoundedRectangleBorder(

View File

@ -7,6 +7,7 @@ import 'package:dismissible_page/dismissible_page.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
@ -144,7 +145,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
mainAxisSize: MainAxisSize.min,
children: [
Text('attachmentAddLinkHint'.tr, textAlign: TextAlign.left),
const SizedBox(height: 18),
const Gap(18),
TextField(
controller: controller,
decoration: InputDecoration(
@ -346,14 +347,16 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
FutureBuilder(
future: element.file.length(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
if (!snapshot.hasData) {
return const SizedBox.shrink();
}
return Text(
_formatBytes(snapshot.data!),
style: Theme.of(context).textTheme.bodySmall,
);
},
),
const SizedBox(width: 6),
const Gap(6),
if (element.progress != null)
Text(
'${(element.progress! * 100).toStringAsFixed(2)}%',
@ -614,7 +617,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 10),
const Gap(10),
Obx(() {
if (_uploadController.isUploading.value) {
return SizedBox(
@ -627,7 +630,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
);
}
return const SizedBox();
return const SizedBox.shrink();
}),
],
),
@ -635,9 +638,9 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
],
),
),
const SizedBox(width: 20),
const Gap(20),
Text('attachmentAutoUpload'.tr),
const SizedBox(width: 8),
const Gap(8),
Switch(
value: _isAutoUpload,
onChanged: (bool? value) {
@ -666,7 +669,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
Obx(() {
if (_uploadController.isUploading.value) {
return const SizedBox();
return const SizedBox.shrink();
}
return TextButton(
child: Text('attachmentUploadQueueStart'.tr),
@ -680,7 +683,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
);
}
return const SliverToBoxAdapter(child: SizedBox());
return const SliverToBoxAdapter(child: SizedBox.shrink());
}),
Obx(() {
if (_uploadController.queueOfUpload.isNotEmpty) {
@ -696,7 +699,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
);
}
return const SliverToBoxAdapter(child: SizedBox());
return const SliverToBoxAdapter(child: SizedBox.shrink());
}),
if (_attachments.isNotEmpty)
SliverPadding(

View File

@ -6,6 +6,7 @@ import 'package:dismissible_page/dismissible_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gal/gal.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/attachment.dart';
@ -198,7 +199,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
radius: 19,
),
),
const IgnorePointer(child: SizedBox(width: 8)),
const Gap(8),
Expanded(
child: IgnorePointer(
child: Column(
@ -240,7 +241,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
),
],
),
const IgnorePointer(child: SizedBox(height: 4)),
const Gap(4),
IgnorePointer(
child: Text(
widget.item.alt,
@ -252,7 +253,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
),
),
),
const IgnorePointer(child: SizedBox(height: 2)),
const Gap(2),
IgnorePointer(
child: Wrap(
spacing: 6,

View File

@ -1,14 +1,20 @@
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:just_audio/just_audio.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/durations.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:video_player/video_player.dart';
class AttachmentItem extends StatefulWidget {
final String parentId;
@ -55,6 +61,11 @@ class _AttachmentItemState extends State<AttachmentItem> {
item: widget.item,
autoload: widget.autoload,
);
case 'audio':
return _AttachmentItemAudio(
item: widget.item,
autoload: widget.autoload,
);
default:
return Center(
child: Container(
@ -65,7 +76,7 @@ class _AttachmentItemState extends State<AttachmentItem> {
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.file_present, size: 32),
const SizedBox(height: 6),
const Gap(6),
Text(
widget.item.mimetype,
style: TextStyle(
@ -74,13 +85,13 @@ class _AttachmentItemState extends State<AttachmentItem> {
),
textAlign: TextAlign.center,
),
const SizedBox(height: 2),
const Gap(2),
Text(
widget.item.alt,
style: const TextStyle(fontSize: 13),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
const Gap(12),
TextButton.icon(
icon: const Icon(Icons.launch),
label: Text('openInBrowser'.tr),
@ -239,22 +250,21 @@ class _AttachmentItemVideo extends StatefulWidget {
}
class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
late final _player = Player(
configuration: const PlayerConfiguration(
logLevel: MPVLogLevel.error,
),
);
late final _controller = VideoController(_player);
bool _showContent = false;
VideoPlayerController? _videoController;
ChewieController? _chewieController;
Future<void> _startLoad() async {
await _player.open(
Media(ServiceFinder.buildUrl('files', '/attachments/${widget.item.rid}')),
play: false,
);
setState(() => _showContent = true);
final url =
ServiceFinder.buildUrl('files', '/attachments/${widget.item.rid}');
_videoController = VideoPlayerController.networkUrl(Uri.parse(url));
await _videoController!.initialize();
_chewieController = ChewieController(
videoPlayerController: _videoController!,
aspectRatio: widget.item.metadata?['ratio'],
);
}
@override
@ -282,7 +292,7 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
color: Colors.white,
size: 32,
),
const SizedBox(height: 8),
const Gap(8),
Text(
'attachmentUnload'.tr,
style: const TextStyle(
@ -304,17 +314,237 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
_startLoad();
},
);
} else if (_chewieController == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Video(
return Chewie(controller: _chewieController!);
}
@override
void dispose() {
_chewieController?.dispose();
_videoController?.dispose();
super.dispose();
}
}
class _AttachmentItemAudio extends StatefulWidget {
final Attachment item;
final bool autoload;
const _AttachmentItemAudio({
required this.item,
this.autoload = false,
});
@override
State<_AttachmentItemAudio> createState() => _AttachmentItemAudioState();
}
class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
bool _showContent = false;
double? _draggingValue;
AudioPlayer? _audioController;
Future<void> _startLoad() async {
setState(() => _showContent = true);
final url =
ServiceFinder.buildUrl('files', '/attachments/${widget.item.rid}');
_audioController = AudioPlayer();
// Platform that can cache image also capable to cache audio
// https://pub.dev/packages/just_audio#experimental-features
if (PlatformInfo.canCacheImage) {
final source = LockCachingAudioSource(Uri.parse(url));
await _audioController!.setAudioSource(source);
} else {
await _audioController!.setUrl(url);
}
_audioController!.playingStream.listen((_) => setState(() {}));
_audioController!.positionStream.listen((_) => setState(() {}));
_audioController!.durationStream.listen((_) => setState(() {}));
_audioController!.bufferedPositionStream.listen((_) => setState(() {}));
}
@override
void initState() {
super.initState();
if (widget.autoload) {
_startLoad();
}
}
@override
Widget build(BuildContext context) {
const ratio = 16 / 9;
if (!_showContent) {
return GestureDetector(
child: AspectRatio(
aspectRatio: ratio,
child: CenteredContainer(
maxWidth: 280,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.not_started,
color: Colors.white,
size: 32,
),
const Gap(8),
Text(
'attachmentUnload'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'attachmentUnloadCaption'.tr,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
],
),
),
),
onTap: () {
_startLoad();
},
);
} else if (_audioController == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return AspectRatio(
aspectRatio: ratio,
controller: _controller,
child: CenteredContainer(
maxWidth: 320,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.audio_file, size: 32),
const Gap(8),
Text(
widget.item.alt,
style: const TextStyle(fontSize: 13),
textAlign: TextAlign.center,
),
const Gap(12),
Row(
children: [
Expanded(
child: Column(
children: [
SliderTheme(
data: SliderThemeData(
trackHeight: 2,
trackShape: _PlayerProgressTrackShape(),
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 8,
),
overlayShape: SliderComponentShape.noOverlay,
),
child: Slider(
secondaryTrackValue: _audioController!
.bufferedPosition.inMilliseconds
.abs()
.toDouble(),
value: _draggingValue?.abs() ??
_audioController!.position.inMilliseconds
.toDouble()
.abs(),
min: 0,
max: max(
_audioController!.bufferedPosition.inMilliseconds
.abs(),
max(
_audioController!.position.inMilliseconds.abs(),
_audioController!.duration?.inMilliseconds
.abs() ??
1,
),
).toDouble(),
onChanged: (value) {
setState(() => _draggingValue = value);
},
onChangeEnd: (value) {
_audioController!
.seek(Duration(milliseconds: value.toInt()));
setState(() => _draggingValue = null);
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_audioController!.position.toHumanReadableString(),
style: GoogleFonts.robotoMono(fontSize: 12),
),
Text(
_audioController!.duration
?.toHumanReadableString() ??
'00:00',
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
).paddingSymmetric(horizontal: 8, vertical: 4),
],
),
),
const Gap(16),
IconButton.filled(
icon: _audioController!.playing
? const Icon(Icons.pause)
: const Icon(Icons.play_arrow),
onPressed: () {
if (_audioController!.playing) {
_audioController!.pause();
} else {
_audioController!.play();
}
},
visualDensity: const VisualDensity(
horizontal: -4,
vertical: 0,
),
),
],
),
],
),
),
);
}
@override
void dispose() {
_player.dispose();
_audioController?.dispose();
super.dispose();
}
}
class _PlayerProgressTrackShape extends RoundedRectSliderTrackShape {
@override
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
required SliderThemeData sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
final trackHeight = sliderTheme.trackHeight;
final trackLeft = offset.dx;
final trackTop = offset.dy + (parentBox.size.height - trackHeight!) / 2;
final trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
}

View File

@ -5,6 +5,7 @@ import 'package:carousel_slider/carousel_slider.dart';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:flutter/material.dart' hide CarouselController;
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/widgets/attachments/attachment_item.dart';
@ -93,6 +94,8 @@ class _AttachmentListState extends State<AttachmentList> {
} else {
portrait++;
}
} else if (entry.mimetype.split('/').firstOrNull == 'audio') {
landscape++;
}
}
if (isConsistent && consistentValue != null) {
@ -138,7 +141,7 @@ class _AttachmentListState extends State<AttachmentList> {
@override
Widget build(BuildContext context) {
if (widget.attachmentsId.isEmpty) {
return const SizedBox();
return const SizedBox.shrink();
}
if (_isLoading) {
@ -171,7 +174,7 @@ class _AttachmentListState extends State<AttachmentList> {
children: widget.attachmentsId.map((x) {
final element = _attachmentsMeta[idx];
idx++;
if (element == null) return const SizedBox();
if (element == null) return const SizedBox.shrink();
double ratio = element.metadata?['ratio']?.toDouble() ?? 16 / 9;
return Container(
decoration: BoxDecoration(
@ -359,7 +362,7 @@ class AttachmentListEntry extends StatelessWidget {
color: Colors.white,
size: 32,
),
if (!isDense) const SizedBox(height: 8),
if (!isDense) const Gap(8),
if (!isDense)
Text(
'matureContent'.tr,

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:avatar_stack/avatar_stack.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/call.dart';
@ -52,27 +53,27 @@ class ChannelCallIndicator extends StatelessWidget {
return Text('callOngoingJoined'.trParams({
'duration': call.lastDuration.value,
}));
} else if (ongoingCall.participants.isEmpty) {
} else if (ongoingCall.participants!.isEmpty) {
return Text('callOngoingEmpty'.tr);
} else {
return Text('callOngoingParticipants'.trParams({
'count': ongoingCall.participants.length.toString(),
'count': ongoingCall.participants!.length.toString(),
}));
}
}),
const SizedBox(width: 6),
const Gap(6),
Obx(() {
if (call.isInitialized.value) {
return const SizedBox();
return const SizedBox.shrink();
}
if (ongoingCall.participants.isNotEmpty) {
if (ongoingCall.participants!.isNotEmpty) {
return Container(
height: 28,
constraints: const BoxConstraints(maxWidth: 120),
child: AvatarStack(
height: 28,
borderWidth: 0,
avatars: ongoingCall.participants.map((x) {
avatars: ongoingCall.participants!.map((x) {
final userinfo =
Account.fromJson(jsonDecode(x['metadata']));
return PlatformInfo.canCacheImage
@ -83,7 +84,7 @@ class ChannelCallIndicator extends StatelessWidget {
),
);
}
return const SizedBox();
return const SizedBox.shrink();
})
],
),
@ -112,7 +113,7 @@ class ChannelCallIndicator extends StatelessWidget {
child: Text('callJoin'.tr),
);
}
return const SizedBox();
return const SizedBox.shrink();
})
],
);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:livekit_client/livekit_client.dart';
@ -35,7 +36,7 @@ class ParticipantInfoWidget extends StatelessWidget {
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 5),
const Gap(5),
isScreenShare
? const Icon(
Icons.monitor,
@ -47,7 +48,7 @@ class ParticipantInfoWidget extends StatelessWidget {
color: audioAvailable ? Colors.white : Colors.red,
size: 16,
),
const SizedBox(width: 3),
const Gap(3),
if (connectionQuality != ConnectionQuality.unknown)
Icon(
{

View File

@ -11,7 +11,7 @@ class ChatCallCurrentIndicator extends StatelessWidget {
return Obx(() {
if (call.current.value == null || call.channel.value == null) {
return const SizedBox();
return const SizedBox.shrink();
}
return ListTile(

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/event.dart';
@ -39,7 +40,7 @@ class ChatEvent extends StatelessWidget {
}
Widget _buildLinkExpansion() {
if (item.body['text'] == null) return const SizedBox();
if (item.body['text'] == null) return const SizedBox.shrink();
return LinkExpansion(content: item.body['text']);
}
@ -48,7 +49,7 @@ class ChatEvent extends StatelessWidget {
? List<String>.from(item.body['attachments']?.whereType<String>())
: List<String>.empty();
if (attachments.isEmpty) return const SizedBox();
if (attachments.isEmpty) return const SizedBox.shrink();
if (isMinimal) {
final unFocusColor =
@ -93,7 +94,7 @@ class ChatEvent extends StatelessWidget {
),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const SizedBox();
return const SizedBox.shrink();
}
return Container(
@ -214,7 +215,7 @@ class ChatEvent extends StatelessWidget {
opacity: 0.75,
child: FaIcon(FontAwesomeIcons.quoteLeft, size: 14),
).paddingOnly(bottom: 2.75),
const SizedBox(width: 4),
const Gap(4),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
@ -226,12 +227,12 @@ class ChatEvent extends StatelessWidget {
content: item.sender.account.avatar,
radius: 9,
),
const SizedBox(width: 5),
const Gap(5),
Text(
item.sender.account.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 4),
const Gap(4),
Text(format(item.createdAt, locale: 'en_short')),
],
),
@ -275,7 +276,7 @@ class ChatEvent extends StatelessWidget {
item.sender.account.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 4),
const Gap(4),
Text(format(item.createdAt, locale: 'en_short'))
],
).paddingSymmetric(horizontal: 12),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
class ChatEventMessageActionLog extends StatelessWidget {
@ -25,7 +26,7 @@ class ChatEventMessageActionLog extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
icon,
const SizedBox(width: 4),
const Gap(4),
Text(text),
],
).paddingOnly(

View File

@ -59,7 +59,8 @@ class ChatEventList extends StatelessWidget {
final item = chatController.currentEvents[index].data;
return InkWell(
return GestureDetector(
behavior: HitTestBehavior.opaque,
child: ChatEvent(
key: Key('m${item.uuid}'),
item: item,
@ -95,7 +96,7 @@ class ChatEventList extends StatelessWidget {
chatController.totalEvents - chatController.currentEvents.length;
if (amount.value <= 0 || chatController.isLoading.isTrue) {
return const SliverToBoxAdapter(child: SizedBox());
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return SliverToBoxAdapter(

View File

@ -156,7 +156,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
};
// The local mock data
final sender = Sender(
final sender = ChannelMember(
id: 0,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/models/channel.dart';
@ -47,7 +48,7 @@ class _ChatTypingIndicatorState extends State<ChatTypingIndicator>
child: Row(
children: [
const Icon(Icons.more_horiz),
const SizedBox(width: 6),
const Gap(6),
Text('typingMessage'.trParams({
'user': widget.users.map((x) => x.account.nick).join(', '),
})),

View File

@ -0,0 +1,253 @@
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/models/daily_sign.dart';
class DailySignHistoryChartDialog extends StatelessWidget {
final List<DailySignRecord>? data;
const DailySignHistoryChartDialog({super.key, required this.data});
static List<String> signSymbols = ['大凶', '', '中平', '', '大吉'];
DateTime? get _firstRecordDate => data?.map((x) => x.createdAt).reduce(
(a, b) => DateTime.fromMillisecondsSinceEpoch(
min(a.millisecondsSinceEpoch, b.millisecondsSinceEpoch)));
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('运势历史'),
Text(
'${DateFormat('yyyy/MM/dd').format(_firstRecordDate!)} - ${DateFormat('yyyy/MM/dd').format(DateTime.now())}',
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
),
),
],
),
content: data == null
? SizedBox(
height: 180,
width: max(640, MediaQuery.of(context).size.width),
child: const Center(
child: CircularProgressIndicator(),
),
)
: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('近期运势', style: Theme.of(context).textTheme.titleMedium)
.paddingOnly(bottom: 18),
SizedBox(
height: 180,
width: max(640, MediaQuery.of(context).size.width),
child: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
isCurved: true,
isStrokeCapRound: true,
isStrokeJoinRound: true,
color: Theme.of(context).colorScheme.primary,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: List.filled(
data!.length,
Theme.of(context)
.colorScheme
.primary
.withOpacity(0.3),
).toList(),
),
),
spots: data!
.map(
(x) => FlSpot(
x.createdAt
.copyWith(
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
microsecond: 0,
)
.millisecondsSinceEpoch
.toDouble(),
x.resultTier.toDouble(),
),
)
.toList(),
)
],
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (spots) => spots
.map((spot) => LineTooltipItem(
'${signSymbols[spot.y.toInt()]}\n${DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(spot.x.toInt()))}',
TextStyle(
color:
Theme.of(context).colorScheme.onSurface,
),
))
.toList(),
getTooltipColor: (_) =>
Theme.of(context).colorScheme.surfaceContainerHigh,
)),
titlesData: FlTitlesData(
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
interval: 1,
getTitlesWidget: (value, _) => Align(
alignment: Alignment.centerRight,
child: Text(
signSymbols[value.toInt()],
textAlign: TextAlign.right,
).paddingOnly(right: 8),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 28,
interval: 86400000,
getTitlesWidget: (value, _) => Text(
DateFormat('dd').format(
DateTime.fromMillisecondsSinceEpoch(
value.toInt(),
),
),
textAlign: TextAlign.center,
).paddingOnly(top: 8),
),
),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
),
),
).marginOnly(right: 24, bottom: 8, top: 8),
const Gap(16),
Text('功德趋势', style: Theme.of(context).textTheme.titleMedium)
.paddingOnly(bottom: 18),
SizedBox(
height: 180,
width: max(640, MediaQuery.of(context).size.width),
child: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
isCurved: true,
isStrokeCapRound: true,
isStrokeJoinRound: true,
color: Theme.of(context).colorScheme.primary,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: List.filled(
data!.length,
Theme.of(context)
.colorScheme
.primary
.withOpacity(0.3),
).toList(),
),
),
spots: data!
.map(
(x) => FlSpot(
x.createdAt
.copyWith(
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
microsecond: 0,
)
.millisecondsSinceEpoch
.toDouble(),
x.resultExperience.toDouble(),
),
)
.toList(),
)
],
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (spots) => spots
.map((spot) => LineTooltipItem(
'+${spot.y.toStringAsFixed(0)} EXP\n${DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(spot.x.toInt()))}',
TextStyle(
color:
Theme.of(context).colorScheme.onSurface,
),
))
.toList(),
getTooltipColor: (_) =>
Theme.of(context).colorScheme.surfaceContainerHigh,
)),
titlesData: FlTitlesData(
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, _) => Align(
alignment: Alignment.centerRight,
child: Text(
value.toStringAsFixed(0),
textAlign: TextAlign.right,
).paddingOnly(right: 8),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 28,
interval: 86400000,
getTitlesWidget: (value, _) => Text(
DateFormat('dd').format(
DateTime.fromMillisecondsSinceEpoch(
value.toInt(),
),
),
textAlign: TextAlign.center,
).paddingOnly(top: 8),
),
),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
),
),
).marginOnly(right: 24, bottom: 8, top: 8),
],
),
);
}
}

View File

@ -57,7 +57,7 @@ class LinkExpansion extends StatelessWidget {
);
final matches = linkRegex.allMatches(content);
if (matches.isEmpty) {
return const SizedBox();
return const SizedBox.shrink();
}
final LinkExpandProvider expandController = Get.find();
@ -72,7 +72,7 @@ class LinkExpansion extends StatelessWidget {
future: expandController.expandLink(x.group(0)!),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
return const SizedBox.shrink();
}
final isRichDescription = [

View File

@ -16,7 +16,7 @@ class AppNavigationRegion extends StatelessWidget {
});
void _gotoChannel(Channel item) {
AppRouter.instance.pushReplacementNamed(
AppRouter.instance.goNamed(
'channelChat',
pathParameters: {'alias': item.alias},
queryParameters: {

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/controllers/post_editor_controller.dart';
@ -86,7 +87,7 @@ class _PostEditorDateDialogState extends State<PostEditorDateDialog> {
),
onTap: () => _selectDate(0),
),
const SizedBox(height: 16),
const Gap(16),
TextField(
controller: _publishedUntilController,
readOnly: true,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/post_editor_controller.dart';
@ -25,7 +26,7 @@ class PostEditorOverviewDialog extends StatelessWidget {
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 16),
const Gap(16),
TextField(
autofocus: true,
autocorrect: true,
@ -37,7 +38,7 @@ class PostEditorOverviewDialog extends StatelessWidget {
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 16),
const Gap(16),
TextField(
enabled: controller.mode.value == 1,
maxLines: null,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/post_editor_controller.dart';
import 'package:solian/widgets/attachments/attachment_editor.dart';
@ -68,7 +69,7 @@ class _PostEditorThumbnailDialogState extends State<PostEditorThumbnailDialog> {
_promptUploadNewAttachment();
},
),
const SizedBox(height: 8),
const Gap(8),
TextField(
controller: _attachmentController,
decoration: InputDecoration(

View File

@ -1,5 +1,6 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/post_editor_controller.dart';
import 'package:solian/widgets/account/account_select.dart';
@ -56,9 +57,9 @@ class PostEditorVisibilityDialog extends StatelessWidget {
Obx(() {
if (controller.visibility.value == 2 ||
controller.visibility.value == 3) {
return const SizedBox(height: 8);
return const Gap(8);
}
return const SizedBox();
return const SizedBox.shrink();
}),
Obx(() {
if (controller.visibility.value == 2) {
@ -85,7 +86,7 @@ class PostEditorVisibilityDialog extends StatelessWidget {
},
);
}
return const SizedBox();
return const SizedBox.shrink();
}),
Obx(() {
if (controller.visibility.value == 3) {
@ -112,7 +113,7 @@ class PostEditorVisibilityDialog extends StatelessWidget {
},
);
}
return const SizedBox();
return const SizedBox.shrink();
}),
],
),

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/services.dart';
@ -111,7 +112,7 @@ class _TagsFieldState extends State<TagsField> {
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 4.0),
const Gap(4),
InkWell(
child: const Icon(
Icons.cancel,

View File

@ -2,6 +2,7 @@ import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart';
import 'package:get/get_utils/get_utils.dart';
import 'package:intl/intl.dart';
import 'package:solian/models/post.dart';
@ -78,7 +79,7 @@ class _PostItemState extends State<PostItem> {
}
Widget _buildThumbnail() {
if (widget.item.body['thumbnail'] == null) return const SizedBox();
if (widget.item.body['thumbnail'] == null) return const SizedBox.shrink();
final border = BorderSide(
color: Theme.of(context).dividerColor,
width: 0.3,
@ -147,7 +148,7 @@ class _PostItemState extends State<PostItem> {
vertical: 8,
);
}
return const SizedBox();
return const SizedBox.shrink();
}
Widget _buildFooter() {
@ -186,7 +187,7 @@ class _PostItemState extends State<PostItem> {
}
if (widgets.isEmpty) {
return const SizedBox();
return const SizedBox.shrink();
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -519,7 +520,7 @@ class _PostItemState extends State<PostItem> {
bottom: 10,
)
else
const SizedBox(height: 10),
const Gap(10),
],
),
openBuilder: (_, __) => TitleShell(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/models/reaction.dart';
@ -33,9 +34,11 @@ class PostReactionPopup extends StatelessWidget {
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(e.key,
style: const TextStyle(fontFamily: 'monospace')),
const SizedBox(width: 6),
Text(
e.key,
style: const TextStyle(fontFamily: 'monospace'),
),
const Gap(6),
Text('x${reactionList[e.key]?.toString() ?? '0'}',
style:
const TextStyle(fontWeight: FontWeight.bold)),

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:solian/models/feed.dart';
import 'package:solian/models/post_categories.dart';
import 'package:solian/router.dart';
class PostTagsList extends StatelessWidget {

View File

@ -29,7 +29,7 @@ class PostWarpedListWidget extends StatelessWidget {
builderDelegate: PagedChildBuilderDelegate<Post>(
itemBuilder: (context, item, index) {
if (item.pinnedAt != null && !isPinned) {
return const SizedBox();
return const SizedBox.shrink();
}
return PostListEntryWidget(
renderOrder: index,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/stickers.dart';
@ -127,7 +128,7 @@ class _StickerUploadDialogState extends State<StickerUploadDialog> {
_promptUploadNewAttachment();
},
),
const SizedBox(height: 8),
const Gap(8),
TextField(
controller: _attachmentController,
decoration: InputDecoration(
@ -138,7 +139,7 @@ class _StickerUploadDialogState extends State<StickerUploadDialog> {
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 8),
const Gap(8),
TextField(
controller: _packController,
decoration: InputDecoration(

View File

@ -11,8 +11,6 @@
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <media_kit_video/media_kit_video_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
@ -32,12 +30,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
g_autoptr(FlPluginRegistrar) media_kit_video_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin");
media_kit_video_plugin_register_with_registrar(media_kit_video_registrar);
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
pasteboard_plugin_register_with_registrar(pasteboard_registrar);

View File

@ -8,14 +8,11 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_acrylic
flutter_secure_storage_linux
flutter_webrtc
media_kit_libs_linux
media_kit_video
pasteboard
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
media_kit_native_event_loop
)
set(PLUGIN_BUNDLED_LIBRARIES)

View File

@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import audio_session
import connectivity_plus
import desktop_drop
import device_info_plus
@ -16,22 +17,22 @@ import firebase_messaging
import flutter_secure_storage_macos
import flutter_webrtc
import gal
import just_audio
import livekit_client
import macos_window_utils
import media_kit_libs_macos_video
import media_kit_video
import package_info_plus
import pasteboard
import path_provider_foundation
import protocol_handler_macos
import screen_brightness_macos
import share_plus
import shared_preferences_foundation
import sqflite
import url_launcher_macos
import video_player_avfoundation
import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
@ -43,18 +44,17 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ProtocolHandlerMacosPlugin.register(with: registry.registrar(forPlugin: "ProtocolHandlerMacosPlugin"))
ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
}

View File

@ -1,4 +1,6 @@
PODS:
- audio_session (0.0.1):
- FlutterMacOS
- connectivity_plus (0.0.1):
- Flutter
- FlutterMacOS
@ -156,17 +158,13 @@ PODS:
- GoogleUtilities/UserDefaults (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- just_audio (0.0.1):
- FlutterMacOS
- livekit_client (2.2.4):
- FlutterMacOS
- WebRTC-SDK (= 125.6422.04)
- macos_window_utils (1.0.0):
- FlutterMacOS
- media_kit_libs_macos_video (1.0.4):
- FlutterMacOS
- media_kit_native_event_loop (1.0.0):
- FlutterMacOS
- media_kit_video (0.0.1):
- FlutterMacOS
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
@ -184,8 +182,6 @@ PODS:
- PromisesObjC (= 2.4.0)
- protocol_handler_macos (0.0.1):
- FlutterMacOS
- screen_brightness_macos (0.1.0):
- FlutterMacOS
- share_plus (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
@ -196,11 +192,15 @@ PODS:
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- FlutterMacOS
- WebRTC-SDK (125.6422.04)
DEPENDENCIES:
- audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`)
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`)
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
@ -213,20 +213,18 @@ DEPENDENCIES:
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
- just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`)
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- protocol_handler_macos (from `Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos`)
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
SPEC REPOS:
@ -250,6 +248,8 @@ SPEC REPOS:
- WebRTC-SDK
EXTERNAL SOURCES:
audio_session:
:path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos
connectivity_plus:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin
desktop_drop:
@ -274,16 +274,12 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral
gal:
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
just_audio:
:path: Flutter/ephemeral/.symlinks/plugins/just_audio/macos
livekit_client:
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
macos_window_utils:
:path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
media_kit_libs_macos_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
media_kit_native_event_loop:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos
media_kit_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
pasteboard:
@ -292,8 +288,6 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
protocol_handler_macos:
:path: Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos
screen_brightness_macos:
:path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos
share_plus:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
@ -302,10 +296,13 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
video_player_avfoundation:
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
wakelock_plus:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
SPEC CHECKSUMS:
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
@ -331,11 +328,9 @@ SPEC CHECKSUMS:
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
just_audio: 9b67ca7b97c61cfc9784ea23cd8cc55eb226d489
livekit_client: 95f3b71e6545845aa658a6df0a3a62dcc3471d7c
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
@ -343,11 +338,11 @@ SPEC CHECKSUMS:
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_macos: d10a6c01d6373389ffd2278013ab4c47ed6d6daa
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3

View File

@ -70,6 +70,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
audio_session:
dependency: transitive
description:
name: audio_session
sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261"
url: "https://pub.dev"
source: hosted
version: "0.1.21"
avatar_stack:
dependency: "direct main"
description:
@ -214,6 +222,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
chewie:
dependency: "direct main"
description:
name: chewie
sha256: "8210c6e8702ddae9b3337aad0d539a9ff128cc4a5baaadc0174edbd0f99b0125"
url: "https://pub.dev"
source: hosted
version: "1.8.4"
cli_util:
dependency: transitive
description:
@ -362,10 +378,10 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
url: "https://pub.dev"
source: hosted
version: "5.6.0"
version: "5.7.0"
dio_web_adapter:
dependency: transitive
description:
@ -390,6 +406,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.9"
equatable:
dependency: transitive
description:
name: equatable
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
url: "https://pub.dev"
source: hosted
version: "2.0.5"
eventify:
dependency: transitive
description:
name: eventify
sha256: b829429f08586cc2001c628e7499e3e3c2493a1d895fd73b00ecb23351aa5a66
url: "https://pub.dev"
source: hosted
version: "1.0.1"
fake_async:
dependency: transitive
description:
@ -582,6 +614,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef"
url: "https://pub.dev"
source: hosted
version: "0.69.0"
floor:
dependency: "direct main"
description:
@ -1117,6 +1157,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.8.0"
just_audio:
dependency: "direct main"
description:
name: just_audio
sha256: d8e8aaf417d33e345299c17f6457f72bd4ba0c549dc34607abb5183a354edc4d
url: "https://pub.dev"
source: hosted
version: "0.9.40"
just_audio_mpv:
dependency: "direct main"
description:
name: just_audio_mpv
sha256: d6e4e9fd20bfb9d2fd5e3dcd7906c90ed07f83d1d2f44f31204160821ab62fed
url: "https://pub.dev"
source: hosted
version: "0.1.7"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790"
url: "https://pub.dev"
source: hosted
version: "4.3.0"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: b163878529d9b028c53a6972fcd58cae2405bcd11cbfcea620b6fb9f151429d6
url: "https://pub.dev"
source: hosted
version: "0.4.12"
just_audio_windows:
dependency: "direct main"
description:
name: just_audio_windows
sha256: "48ab2dec79cf6097550602fe07b1a644f341450e138dc8fdc23e42ce0ed2d928"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
leak_tracker:
dependency: transitive
description:
@ -1221,78 +1301,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.11.1"
media_kit:
dependency: "direct main"
description:
name: media_kit
sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62"
url: "https://pub.dev"
source: hosted
version: "1.1.11"
media_kit_libs_android_video:
dependency: transitive
description:
name: media_kit_libs_android_video
sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c"
url: "https://pub.dev"
source: hosted
version: "1.3.6"
media_kit_libs_ios_video:
dependency: transitive
description:
name: media_kit_libs_ios_video
sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991
url: "https://pub.dev"
source: hosted
version: "1.1.4"
media_kit_libs_linux:
dependency: transitive
description:
name: media_kit_libs_linux
sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310
url: "https://pub.dev"
source: hosted
version: "1.1.3"
media_kit_libs_macos_video:
dependency: transitive
description:
name: media_kit_libs_macos_video
sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d
url: "https://pub.dev"
source: hosted
version: "1.1.4"
media_kit_libs_video:
dependency: "direct main"
description:
name: media_kit_libs_video
sha256: "20bb4aefa8fece282b59580e1cd8528117297083a6640c98c2e98cfc96b93288"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
media_kit_libs_windows_video:
dependency: transitive
description:
name: media_kit_libs_windows_video
sha256: "32654572167825c42c55466f5d08eee23ea11061c84aa91b09d0e0f69bdd0887"
url: "https://pub.dev"
source: hosted
version: "1.0.10"
media_kit_native_event_loop:
dependency: transitive
description:
name: media_kit_native_event_loop
sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40"
url: "https://pub.dev"
source: hosted
version: "1.0.9"
media_kit_video:
dependency: "direct main"
description:
name: media_kit_video
sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f"
url: "https://pub.dev"
source: hosted
version: "1.2.5"
meta:
dependency: transitive
description:
@ -1309,6 +1317,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.6"
mpv_dart:
dependency: transitive
description:
name: mpv_dart
sha256: a33bd9a68439b496b7a5f36fecd3aa3cf6cbf0176ae15b9b60b12ae96e58f5a4
url: "https://pub.dev"
source: hosted
version: "0.0.1"
nested:
dependency: transitive
description:
@ -1645,62 +1661,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.28.0"
safe_local_storage:
dependency: transitive
description:
name: safe_local_storage
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440
url: "https://pub.dev"
source: hosted
version: "1.0.2"
screen_brightness:
dependency: transitive
description:
name: screen_brightness
sha256: ed8da4a4511e79422fc1aa88138e920e4008cd312b72cdaa15ccb426c0faaedd
url: "https://pub.dev"
source: hosted
version: "0.2.2+1"
screen_brightness_android:
dependency: transitive
description:
name: screen_brightness_android
sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf"
url: "https://pub.dev"
source: hosted
version: "0.1.0+2"
screen_brightness_ios:
dependency: transitive
description:
name: screen_brightness_ios
sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
screen_brightness_macos:
dependency: transitive
description:
name: screen_brightness_macos
sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd"
url: "https://pub.dev"
source: hosted
version: "0.1.0+1"
screen_brightness_platform_interface:
dependency: transitive
description:
name: screen_brightness_platform_interface
sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171
url: "https://pub.dev"
source: hosted
version: "0.1.0"
screen_brightness_windows:
dependency: transitive
description:
name: screen_brightness_windows
sha256: "9261bf33d0fc2707d8cf16339ce25768100a65e70af0fcabaf032fc12408ba86"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
sdp_transform:
dependency: transitive
description:
@ -1994,22 +1954,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.2"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
uri_parser:
dependency: transitive
description:
name: uri_parser
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
url_launcher:
dependency: "direct main"
description:
@ -2114,6 +2058,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
video_player:
dependency: "direct main"
description:
name: video_player
sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d
url: "https://pub.dev"
source: hosted
version: "2.9.1"
video_player_android:
dependency: transitive
description:
name: video_player_android
sha256: "38d8fe136c427abdce68b5e8c3c08ea29d7a794b453c7a51b12ecfad4aad9437"
url: "https://pub.dev"
source: hosted
version: "2.7.3"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c
url: "https://pub.dev"
source: hosted
version: "2.6.1"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6"
url: "https://pub.dev"
source: hosted
version: "6.2.2"
video_player_web:
dependency: transitive
description:
name: video_player_web
sha256: "6dcdd298136523eaf7dfc31abaf0dfba9aa8a8dbc96670e87e9d42b6f2caf774"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
video_player_win:
dependency: "direct main"
description:
name: video_player_win
sha256: e7b92ae1050086f9be3787eba1fbecbea278b59453cb3589263daa0c62772a88
url: "https://pub.dev"
source: hosted
version: "2.3.11"
vm_service:
dependency: transitive
description:
@ -2122,14 +2114,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "14.2.5"
volume_controller:
dependency: transitive
description:
name: volume_controller
sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e
url: "https://pub.dev"
source: hosted
version: "2.0.8"
wakelock_plus:
dependency: "direct main"
description:

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App"
publish_to: "none"
version: 1.2.1+27
version: 1.2.1+30
environment:
sdk: ">=3.3.4 <4.0.0"
@ -68,15 +68,19 @@ dependencies:
firebase_crashlytics: ^4.0.4
firebase_analytics: ^11.2.1
firebase_performance: ^0.10.0+4
media_kit: ^1.1.10+1
media_kit_video: ^1.2.4
media_kit_libs_video: ^1.0.4
flutter_svg: ^2.0.10+1
cross_file: ^0.3.4+2
google_fonts: ^6.2.1
freezed_annotation: ^2.4.4
json_annotation: ^4.9.0
gap: ^3.0.1
fl_chart: ^0.69.0
video_player: ^2.9.1
video_player_win: ^2.3.11
chewie: ^1.8.4
just_audio: ^0.9.40
just_audio_windows: ^0.2.1
just_audio_mpv: ^0.1.7
dev_dependencies:
flutter_test:

View File

@ -31,40 +31,6 @@
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png">
<!-- Loading styles -->
<style>
.loader-container {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.loader {
border: 10px solid #f3f3f3;
border-top: 10px solid #8f94ca;
border-radius: 50%;
width: 80px;
height: 80px;
animation: spin 0.35s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<title>Solian</title>
<link rel="manifest" href="manifest.json">
@ -150,10 +116,6 @@
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
</picture>
<div class="loader-container">
<div class="loader"></div>
</div>
<script src="flutter_bootstrap.js" async=""></script>

View File

@ -14,15 +14,14 @@
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <gal/gal_plugin_c_api.h>
#include <just_audio_windows/just_audio_windows_plugin.h>
#include <livekit_client/live_kit_plugin.h>
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <video_player_win/video_player_win_plugin_c_api.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
@ -41,22 +40,20 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
GalPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GalPluginCApi"));
JustAudioWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("JustAudioWindowsPlugin"));
LiveKitPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LiveKitPlugin"));
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
MediaKitVideoPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
ScreenBrightnessWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
VideoPlayerWinPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("VideoPlayerWinPluginCApi"));
}

View File

@ -11,19 +11,17 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
flutter_webrtc
gal
just_audio_windows
livekit_client
media_kit_libs_windows_video
media_kit_video
pasteboard
permission_handler_windows
protocol_handler_windows
screen_brightness_windows
share_plus
url_launcher_windows
video_player_win
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
media_kit_native_event_loop
)
set(PLUGIN_BUNDLED_LIBRARIES)