Compare commits
9 Commits
2.3.2+75
...
1b41c847a6
Author | SHA1 | Date | |
---|---|---|---|
1b41c847a6 | |||
b1af6c2c97 | |||
8e76ff3f84 | |||
bd26602299 | |||
52ab1d0d10 | |||
f746e06f65 | |||
d11069a2be | |||
d6dc487d9e | |||
a07c7cdede |
18
api/Passport/Deal Abuse Report.bru
Normal file
18
api/Passport/Deal Abuse Report.bru
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
meta {
|
||||||
|
name: Deal Abuse Report
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
put {
|
||||||
|
url: {{endpoint}}/cgi/id/reports/abuse/3/status
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"status": "processed",
|
||||||
|
"message": "相关附件已经进行评级处理,未来会将该项权限下放到帖主以及社区成员。"
|
||||||
|
}
|
||||||
|
}
|
@ -203,6 +203,11 @@
|
|||||||
"other": "{} comments"
|
"other": "{} comments"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "Appearance",
|
"settingsAppearance": "Appearance",
|
||||||
|
"settingsCustomFonts": "Custom Fonts",
|
||||||
|
"settingsCustomFontsDescription": "Set custom fonts for the application.",
|
||||||
|
"settingsCustomFontFamily": "Custom Font Family",
|
||||||
|
"settingsCustomFontFamilyHint": "Use comma to separate fonts, higher priority comes first",
|
||||||
|
"settingsCustomFontApplied": "Custom font has been applied.",
|
||||||
"settingsDisplayLanguage": "Display Language",
|
"settingsDisplayLanguage": "Display Language",
|
||||||
"settingsDisplayLanguageDescription": "Set the application language.",
|
"settingsDisplayLanguageDescription": "Set the application language.",
|
||||||
"settingsDisplayLanguageSystem": "Follow System",
|
"settingsDisplayLanguageSystem": "Follow System",
|
||||||
|
@ -201,6 +201,11 @@
|
|||||||
"other": "{} 条评论"
|
"other": "{} 条评论"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "外观",
|
"settingsAppearance": "外观",
|
||||||
|
"settingsCustomFonts": "自定义字体",
|
||||||
|
"settingsCustomFontsDescription": "设置应用程序使用的字体。",
|
||||||
|
"settingsCustomFontFamily": "应用字体",
|
||||||
|
"settingsCustomFontFamilyHint": "使用英文逗号分割每一种字体,越前优先级越高",
|
||||||
|
"settingsCustomFontApplied": "自定义字体已经应用。",
|
||||||
"settingsDisplayLanguage": "显示语言",
|
"settingsDisplayLanguage": "显示语言",
|
||||||
"settingsDisplayLanguageDescription": "设置应用程序使用的语言",
|
"settingsDisplayLanguageDescription": "设置应用程序使用的语言",
|
||||||
"settingsDisplayLanguageSystem": "跟随系统",
|
"settingsDisplayLanguageSystem": "跟随系统",
|
||||||
|
@ -201,6 +201,11 @@
|
|||||||
"other": "{} 條評論"
|
"other": "{} 條評論"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "外觀",
|
"settingsAppearance": "外觀",
|
||||||
|
"settingsCustomFonts": "自定義字體",
|
||||||
|
"settingsCustomFontsDescription": "設置應用程序使用的字體。",
|
||||||
|
"settingsCustomFontFamily": "應用字體",
|
||||||
|
"settingsCustomFontFamilyHint": "使用英文逗號分割每一種字體,越前優先級越高",
|
||||||
|
"settingsCustomFontApplied": "自定義字體已經應用。",
|
||||||
"settingsDisplayLanguage": "顯示語言",
|
"settingsDisplayLanguage": "顯示語言",
|
||||||
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
|
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
|
||||||
"settingsDisplayLanguageSystem": "跟隨系統",
|
"settingsDisplayLanguageSystem": "跟隨系統",
|
||||||
|
@ -201,6 +201,11 @@
|
|||||||
"other": "{} 條評論"
|
"other": "{} 條評論"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "外觀",
|
"settingsAppearance": "外觀",
|
||||||
|
"settingsCustomFonts": "自定義字體",
|
||||||
|
"settingsCustomFontsDescription": "設置應用程序使用的字體。",
|
||||||
|
"settingsCustomFontFamily": "應用字體",
|
||||||
|
"settingsCustomFontFamilyHint": "使用英文逗號分割每一種字體,越前優先級越高",
|
||||||
|
"settingsCustomFontApplied": "自定義字體已經應用。",
|
||||||
"settingsDisplayLanguage": "顯示語言",
|
"settingsDisplayLanguage": "顯示語言",
|
||||||
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
|
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
|
||||||
"settingsDisplayLanguageSystem": "跟隨系統",
|
"settingsDisplayLanguageSystem": "跟隨系統",
|
||||||
|
@ -235,7 +235,7 @@ PODS:
|
|||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (~> 3.49.0)
|
- sqlite3 (~> 3.49.1)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
@ -445,7 +445,7 @@ SPEC CHECKSUMS:
|
|||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
||||||
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
|
sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
||||||
|
@ -18,6 +18,7 @@ const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
|||||||
const kAppExpandPostLink = 'app_expand_post_link';
|
const kAppExpandPostLink = 'app_expand_post_link';
|
||||||
const kAppExpandChatLink = 'app_expand_chat_link';
|
const kAppExpandChatLink = 'app_expand_chat_link';
|
||||||
const kAppRealmCompactView = 'app_realm_compact_view';
|
const kAppRealmCompactView = 'app_realm_compact_view';
|
||||||
|
const kAppCustomFonts = 'app_custom_fonts';
|
||||||
|
|
||||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||||
'settingsImageQualityLowest': FilterQuality.none,
|
'settingsImageQualityLowest': FilterQuality.none,
|
||||||
|
@ -13,8 +13,16 @@ class ThemeProvider extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadTheme({Color? seedColorOverride, bool? useMaterial3}) {
|
void reloadTheme({
|
||||||
createAppThemeSet(seedColorOverride: seedColorOverride, useMaterial3: useMaterial3).then((value) {
|
Color? seedColorOverride,
|
||||||
|
bool? useMaterial3,
|
||||||
|
String? customFonts,
|
||||||
|
}) {
|
||||||
|
createAppThemeSet(
|
||||||
|
seedColorOverride: seedColorOverride,
|
||||||
|
useMaterial3: useMaterial3,
|
||||||
|
customFonts: customFonts,
|
||||||
|
).then((value) {
|
||||||
theme = value;
|
theme = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
|
@ -27,8 +27,11 @@ class UserDirectoryProvider {
|
|||||||
plannedQuery.add(item);
|
plannedQuery.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final resp = await _sn.client.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')});
|
if (plannedQuery.isEmpty) return out;
|
||||||
final respDecoded = resp.data.map((e) => SnAccount.fromJson(e)).cast<SnAccount>().toList();
|
final resp = await _sn.client
|
||||||
|
.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')});
|
||||||
|
final respDecoded =
|
||||||
|
resp.data.map((e) => SnAccount.fromJson(e)).cast<SnAccount>().toList();
|
||||||
var sideIdx = 0;
|
var sideIdx = 0;
|
||||||
for (var idx = 0; idx < out.length; idx++) {
|
for (var idx = 0; idx < out.length; idx++) {
|
||||||
if (out[idx] != null) continue;
|
if (out[idx] != null) continue;
|
||||||
|
@ -43,7 +43,8 @@ class UserScreen extends StatefulWidget {
|
|||||||
State<UserScreen> createState() => _UserScreenState();
|
State<UserScreen> createState() => _UserScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateMixin {
|
class _UserScreenState extends State<UserScreen>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
late final ScrollController _scrollController = ScrollController();
|
late final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
SnAccount? _account;
|
SnAccount? _account;
|
||||||
@ -64,13 +65,18 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SnCheckInRecord>> _getCheckInRecords() async {
|
List<SnCheckInRecord>? _records;
|
||||||
|
|
||||||
|
Future<void> _getCheckInRecords() async {
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/users/${widget.name}/check-in?take=14');
|
final resp =
|
||||||
return List.from(
|
await sn.client.get('/cgi/id/users/${widget.name}/check-in?take=14');
|
||||||
|
setState(() {
|
||||||
|
_records = List.from(
|
||||||
resp.data['data']?.map((x) => SnCheckInRecord.fromJson(x)) ?? [],
|
resp.data['data']?.map((x) => SnCheckInRecord.fromJson(x)) ?? [],
|
||||||
);
|
);
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (mounted) context.showErrorDialog(err);
|
if (mounted) context.showErrorDialog(err);
|
||||||
rethrow;
|
rethrow;
|
||||||
@ -98,7 +104,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
Future<void> _fetchPublishers() async {
|
Future<void> _fetchPublishers() async {
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/publishers?user=${widget.name}');
|
final resp =
|
||||||
|
await sn.client.get('/cgi/co/publishers?user=${widget.name}');
|
||||||
_publishers = List<SnPublisher>.from(
|
_publishers = List<SnPublisher>.from(
|
||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
@ -144,7 +151,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
'related': _account!.name,
|
'related': _account!.name,
|
||||||
});
|
});
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
|
context.showSnackbar(
|
||||||
|
'userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -160,9 +168,11 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final rel = context.read<SnRelationshipProvider>();
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {});
|
await rel.updateRelationship(
|
||||||
|
_account!.id, 1, _accountRelationship?.permNodes ?? {});
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
|
context.showSnackbar(
|
||||||
|
'userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -188,12 +198,14 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
double _appBarBlur = 0.0;
|
double _appBarBlur = 0.0;
|
||||||
|
|
||||||
late final _appBarWidth = MediaQuery.of(context).size.width;
|
late final _appBarWidth = MediaQuery.of(context).size.width;
|
||||||
late final _appBarHeight = (_appBarWidth * kBannerAspectRatio).roundToDouble();
|
late final _appBarHeight =
|
||||||
|
(_appBarWidth * kBannerAspectRatio).roundToDouble();
|
||||||
|
|
||||||
void _updateAppBarBlur() {
|
void _updateAppBarBlur() {
|
||||||
if (_scrollController.offset > _appBarHeight) return;
|
if (_scrollController.offset > _appBarHeight) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_appBarBlur = (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
|
_appBarBlur =
|
||||||
|
(_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +217,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
_fetchStatus();
|
_fetchStatus();
|
||||||
_fetchPublishers();
|
_fetchPublishers();
|
||||||
|
_getCheckInRecords();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final rel = context.read<SnRelationshipProvider>();
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
@ -260,7 +273,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: _account!.nick,
|
text: _account!.nick,
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style:
|
||||||
|
Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
),
|
),
|
||||||
@ -268,7 +282,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
const TextSpan(text: '\n'),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '@${_account!.name}',
|
text: '@${_account!.name}',
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
style:
|
||||||
|
Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
),
|
),
|
||||||
@ -339,7 +354,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity:
|
||||||
|
VisualDensity(horizontal: -4, vertical: -4),
|
||||||
),
|
),
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
@ -399,7 +415,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
Symbols.circle,
|
Symbols.circle,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: (_status?.isOnline ?? false) ? Colors.green : Colors.grey,
|
color: (_status?.isOnline ?? false)
|
||||||
|
? Colors.green
|
||||||
|
: Colors.grey,
|
||||||
).padding(all: 4),
|
).padding(all: 4),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
@ -409,7 +427,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
: 'accountStatusOffline'.tr()
|
: 'accountStatusOffline'.tr()
|
||||||
: 'loading'.tr(),
|
: 'loading'.tr(),
|
||||||
),
|
),
|
||||||
if (_status != null && !_status!.isOnline && _status!.lastSeenAt != null)
|
if (_status != null &&
|
||||||
|
!_status!.isOnline &&
|
||||||
|
_status!.lastSeenAt != null)
|
||||||
Text(
|
Text(
|
||||||
'accountStatusLastSeen'.tr(args: [
|
'accountStatusLastSeen'.tr(args: [
|
||||||
_status!.lastSeenAt != null
|
_status!.lastSeenAt != null
|
||||||
@ -429,11 +449,14 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
(ele) => Tooltip(
|
(ele) => Tooltip(
|
||||||
richMessage: TextSpan(
|
richMessage: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
|
TextSpan(
|
||||||
|
text: kBadgesMeta[ele.type]?.$1.tr() ??
|
||||||
|
'unknown'.tr()),
|
||||||
if (ele.metadata['title'] != null)
|
if (ele.metadata['title'] != null)
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '\n${ele.metadata['title']}',
|
text: '\n${ele.metadata['title']}',
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
TextSpan(text: '\n'),
|
TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
@ -442,7 +465,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
|
kBadgesMeta[ele.type]?.$2 ??
|
||||||
|
Symbols.question_mark,
|
||||||
color: kBadgesMeta[ele.type]?.$3,
|
color: kBadgesMeta[ele.type]?.$3,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
),
|
),
|
||||||
@ -458,7 +482,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.calendar_add_on),
|
const Icon(Symbols.calendar_add_on),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text('publisherJoinedAt').tr(args: [DateFormat('y/M/d').format(_account!.createdAt)]),
|
Text('publisherJoinedAt').tr(args: [
|
||||||
|
DateFormat('y/M/d').format(_account!.createdAt)
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@ -491,17 +517,24 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.star),
|
const Icon(Symbols.star),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text('Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'),
|
Text(
|
||||||
|
'Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(calcLevelUpProgressLevel(_account?.profile?.experience ?? 0)).fontSize(11).opacity(0.5),
|
Text(calcLevelUpProgressLevel(
|
||||||
|
_account?.profile?.experience ?? 0))
|
||||||
|
.fontSize(11)
|
||||||
|
.opacity(0.5),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
constraints: const BoxConstraints(maxWidth: 160),
|
constraints: const BoxConstraints(maxWidth: 160),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: calcLevelUpProgress(_account?.profile?.experience ?? 0),
|
value: calcLevelUpProgress(
|
||||||
|
_account?.profile?.experience ?? 0),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
backgroundColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainer,
|
||||||
).alignment(Alignment.centerLeft),
|
).alignment(Alignment.centerLeft),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -514,21 +547,23 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
SliverToBoxAdapter(child: const Divider()),
|
SliverToBoxAdapter(child: const Divider()),
|
||||||
const SliverGap(12),
|
const SliverGap(12),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: FutureBuilder<List<SnCheckInRecord>>(
|
child: Builder(
|
||||||
future: _getCheckInRecords(),
|
builder: (context) {
|
||||||
builder: (context, snapshot) {
|
if (_records == null) return const SizedBox.shrink();
|
||||||
if (!snapshot.hasData) return const SizedBox.shrink();
|
if (_records!.length <= 1) {
|
||||||
if (snapshot.data!.length <= 1) {
|
|
||||||
return Text(
|
return Text(
|
||||||
'accountCheckInNoRecords',
|
'accountCheckInNoRecords',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
).tr().fontWeight(FontWeight.bold).center().padding(horizontal: 20, vertical: 8);
|
)
|
||||||
|
.tr()
|
||||||
|
.fontWeight(FontWeight.bold)
|
||||||
|
.center()
|
||||||
|
.padding(horizontal: 20, vertical: 8);
|
||||||
}
|
}
|
||||||
final records = snapshot.data!;
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 240,
|
height: 240,
|
||||||
child: CheckInRecordChart(records: records),
|
child: CheckInRecordChart(records: _records!),
|
||||||
).padding(
|
).padding(
|
||||||
right: 24,
|
right: 24,
|
||||||
left: 16,
|
left: 16,
|
||||||
@ -544,7 +579,11 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('accountBadge').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
Text('accountBadge')
|
||||||
|
.bold()
|
||||||
|
.fontSize(17)
|
||||||
|
.tr()
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 80,
|
height: 80,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@ -558,7 +597,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
child: Card(
|
child: Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
kBadgesMeta[badge.type]?.$2 ?? Symbols.question_mark,
|
kBadgesMeta[badge.type]?.$2 ??
|
||||||
|
Symbols.question_mark,
|
||||||
color: kBadgesMeta[badge.type]?.$3,
|
color: kBadgesMeta[badge.type]?.$3,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
),
|
),
|
||||||
@ -568,7 +608,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
subtitle: badge.metadata['title'] != null
|
subtitle: badge.metadata['title'] != null
|
||||||
? Text(badge.metadata['title'])
|
? Text(badge.metadata['title'])
|
||||||
: Text(
|
: Text(
|
||||||
DateFormat('y/M/d').format(badge.createdAt),
|
DateFormat('y/M/d')
|
||||||
|
.format(badge.createdAt),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -664,7 +705,8 @@ class CheckInRecordChart extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
getTooltipColor: (_) => Theme.of(context).colorScheme.surfaceContainerHigh,
|
getTooltipColor: (_) =>
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
|
@ -74,18 +74,20 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
|
final idSet = <int>{};
|
||||||
for (final channel in channels) {
|
for (final channel in channels) {
|
||||||
if (channel.type == 1) {
|
if (channel.type == 1) {
|
||||||
await ud.listAccount(
|
idSet.addAll(
|
||||||
channel.members
|
channel.members
|
||||||
?.cast<SnChannelMember?>()
|
?.cast<SnChannelMember?>()
|
||||||
.map((ele) => ele?.accountId)
|
.map((ele) => ele?.accountId)
|
||||||
.where((ele) => ele != null)
|
.where((ele) => ele != null)
|
||||||
.toSet() ??
|
.cast<int>() ??
|
||||||
{},
|
[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (idSet.isNotEmpty) await ud.listAccount(idSet);
|
||||||
|
|
||||||
if (mounted) setState(() => _channels = channels);
|
if (mounted) setState(() => _channels = channels);
|
||||||
})
|
})
|
||||||
@ -290,10 +292,34 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: lastMessage != null
|
subtitle: lastMessage != null
|
||||||
? Text(
|
? Row(
|
||||||
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
lastMessage.body['text'] ??
|
||||||
|
'Unable preview',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
DateFormat(
|
||||||
|
lastMessage.createdAt.toLocal().day ==
|
||||||
|
DateTime.now().day
|
||||||
|
? 'HH:mm'
|
||||||
|
: lastMessage.createdAt
|
||||||
|
.toLocal()
|
||||||
|
.year ==
|
||||||
|
DateTime.now().year
|
||||||
|
? 'MM/dd'
|
||||||
|
: 'yy/MM/dd',
|
||||||
|
).format(lastMessage.createdAt.toLocal()),
|
||||||
|
style: GoogleFonts.robotoMono(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
channel.description,
|
channel.description,
|
||||||
@ -336,6 +362,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
'unknown'.tr()),
|
'unknown'.tr()),
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
Theme.of(context).colorScheme.primary,
|
Theme.of(context).colorScheme.primary,
|
||||||
|
textColor:
|
||||||
|
Theme.of(context).colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -346,6 +374,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
DateFormat(
|
DateFormat(
|
||||||
lastMessage.createdAt.toLocal().day ==
|
lastMessage.createdAt.toLocal().day ==
|
||||||
|
@ -29,6 +29,7 @@ const Map<String, IconData> kNotificationTopicIcons = {
|
|||||||
'passport.security.otp': Symbols.password,
|
'passport.security.otp': Symbols.password,
|
||||||
'interactive.subscription': Symbols.subscriptions,
|
'interactive.subscription': Symbols.subscriptions,
|
||||||
'interactive.feedback': Symbols.add_reaction,
|
'interactive.feedback': Symbols.add_reaction,
|
||||||
|
'interactive.reply': Symbols.reply,
|
||||||
'messaging.callStart': Symbols.call_received,
|
'messaging.callStart': Symbols.call_received,
|
||||||
'wallet.transaction.new': Symbols.receipt,
|
'wallet.transaction.new': Symbols.receipt,
|
||||||
};
|
};
|
||||||
@ -57,10 +58,17 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final nty = context.read<NotificationProvider>();
|
final nty = context.read<NotificationProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/notifications?take=10');
|
final resp =
|
||||||
|
await sn.client.get('/cgi/id/notifications', queryParameters: {
|
||||||
|
'take': 10,
|
||||||
|
'offset': _notifications.length,
|
||||||
|
});
|
||||||
_totalCount = resp.data['count'];
|
_totalCount = resp.data['count'];
|
||||||
_notifications.addAll(
|
_notifications.addAll(
|
||||||
resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? [],
|
resp.data['data']
|
||||||
|
?.map((e) => SnNotification.fromJson(e))
|
||||||
|
.cast<SnNotification>() ??
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
nty.updateTray();
|
nty.updateTray();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -186,7 +194,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
_fetchNotifications();
|
_fetchNotifications();
|
||||||
},
|
},
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!,
|
hasReachedMax: _totalCount != null &&
|
||||||
|
_notifications.length >= _totalCount!,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final nty = _notifications[idx];
|
final nty = _notifications[idx];
|
||||||
return Row(
|
return Row(
|
||||||
@ -218,13 +227,17 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
isAutoWarp: true,
|
isAutoWarp: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription']
|
if ([
|
||||||
.contains(nty.topic) &&
|
'interactive.reply',
|
||||||
|
'interactive.feedback',
|
||||||
|
'interactive.subscription'
|
||||||
|
].contains(nty.topic) &&
|
||||||
nty.metadata['related_post'] != null)
|
nty.metadata['related_post'] != null)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8)),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1,
|
width: 1,
|
||||||
@ -243,7 +256,9 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postDetail',
|
'postDetail',
|
||||||
pathParameters: {
|
pathParameters: {
|
||||||
'slug': nty.metadata['related_post']!['id'].toString(),
|
'slug': nty
|
||||||
|
.metadata['related_post']!['id']
|
||||||
|
.toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -272,8 +287,10 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.check),
|
icon: const Icon(Symbols.check),
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity:
|
||||||
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty),
|
const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
onPressed:
|
||||||
|
_isSubmitting ? null : () => _markOneAsRead(nty),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16);
|
).padding(horizontal: 16);
|
||||||
|
@ -48,6 +48,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
late final SharedPreferences _prefs;
|
late final SharedPreferences _prefs;
|
||||||
String _docBasepath = '/';
|
String _docBasepath = '/';
|
||||||
|
|
||||||
|
final TextEditingController _customFontController = TextEditingController();
|
||||||
final TextEditingController _serverUrlController = TextEditingController();
|
final TextEditingController _serverUrlController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -62,11 +63,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
final config = context.read<ConfigProvider>();
|
final config = context.read<ConfigProvider>();
|
||||||
_prefs = config.prefs;
|
_prefs = config.prefs;
|
||||||
_serverUrlController.text = config.serverUrl;
|
_serverUrlController.text = config.serverUrl;
|
||||||
|
if (_prefs.getString(kAppCustomFonts) != null) {
|
||||||
|
_customFontController.text = _prefs.getString(kAppCustomFonts) ?? '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_serverUrlController.dispose();
|
_serverUrlController.dispose();
|
||||||
|
_customFontController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,6 +335,47 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.font_download),
|
||||||
|
title: Text('settingsCustomFonts').tr(),
|
||||||
|
subtitle: Text('settingsCustomFontsDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 14),
|
||||||
|
trailing: IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
onPressed: () {
|
||||||
|
_prefs.remove(kAppCustomFonts);
|
||||||
|
context.showSnackbar('settingsCustomFontApplied'.tr());
|
||||||
|
final theme = context.read<ThemeProvider>();
|
||||||
|
_customFontController.clear();
|
||||||
|
theme.reloadTheme();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: _customFontController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: 'settingsCustomFontFamily'.tr(),
|
||||||
|
helperText: 'settingsCustomFontFamilyHint'.tr(),
|
||||||
|
prefixIcon: const Icon(Symbols.format_paint),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
onPressed: () {
|
||||||
|
_prefs.setString(
|
||||||
|
kAppCustomFonts,
|
||||||
|
_customFontController.text,
|
||||||
|
);
|
||||||
|
context.showSnackbar('settingsCustomFontApplied'.tr());
|
||||||
|
final theme = context.read<ThemeProvider>();
|
||||||
|
theme.reloadTheme();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16, top: 8, bottom: 4),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
|
@ -179,7 +179,9 @@ class _StickerScreenState extends State<StickerScreen>
|
|||||||
child: InfiniteList(
|
child: InfiniteList(
|
||||||
itemCount: _packs.length,
|
itemCount: _packs.length,
|
||||||
onFetchData: _fetchPacks,
|
onFetchData: _fetchPacks,
|
||||||
hasReachedMax: _totalCount != null && _packs.length >= _totalCount!,
|
hasReachedMax:
|
||||||
|
(_totalCount != null && _packs.length >= _totalCount!) ||
|
||||||
|
_tabController.index == 2,
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final pack = _packs[idx];
|
final pack = _packs[idx];
|
||||||
|
@ -11,10 +11,19 @@ class ThemeSet {
|
|||||||
ThemeSet({required this.light, required this.dark});
|
ThemeSet({required this.light, required this.dark});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ThemeSet> createAppThemeSet({Color? seedColorOverride, bool? useMaterial3}) async {
|
Future<ThemeSet> createAppThemeSet(
|
||||||
|
{Color? seedColorOverride, bool? useMaterial3, String? customFonts}) async {
|
||||||
return ThemeSet(
|
return ThemeSet(
|
||||||
light: await createAppTheme(Brightness.light, useMaterial3: useMaterial3),
|
light: await createAppTheme(
|
||||||
dark: await createAppTheme(Brightness.dark, useMaterial3: useMaterial3),
|
Brightness.light,
|
||||||
|
useMaterial3: useMaterial3,
|
||||||
|
customFonts: customFonts,
|
||||||
|
),
|
||||||
|
dark: await createAppTheme(
|
||||||
|
Brightness.dark,
|
||||||
|
useMaterial3: useMaterial3,
|
||||||
|
customFonts: customFonts,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,24 +31,35 @@ Future<ThemeData> createAppTheme(
|
|||||||
Brightness brightness, {
|
Brightness brightness, {
|
||||||
Color? seedColorOverride,
|
Color? seedColorOverride,
|
||||||
bool? useMaterial3,
|
bool? useMaterial3,
|
||||||
|
String? customFonts,
|
||||||
}) async {
|
}) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
final seedColorString = prefs.getInt(kAppColorSchemeStoreKey);
|
final seedColorString = prefs.getInt(kAppColorSchemeStoreKey);
|
||||||
final seedColor = seedColorString != null ? Color(seedColorString) : Colors.indigo;
|
final seedColor =
|
||||||
|
seedColorString != null ? Color(seedColorString) : Colors.indigo;
|
||||||
|
|
||||||
final colorScheme = ColorScheme.fromSeed(
|
final colorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: seedColorOverride ?? seedColor,
|
seedColor: seedColorOverride ?? seedColor,
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
);
|
);
|
||||||
|
|
||||||
final hasAppBarTransparent = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
final hasAppBarTransparent =
|
||||||
final useM3 = useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
|
prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
||||||
|
final useM3 =
|
||||||
|
useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
|
||||||
|
|
||||||
|
final inUseFonts = (customFonts ?? prefs.getString(kAppCustomFonts))
|
||||||
|
?.split(',')
|
||||||
|
.map((ele) => ele.trim())
|
||||||
|
.toList();
|
||||||
|
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
useMaterial3: useM3,
|
useMaterial3: useM3,
|
||||||
colorScheme: colorScheme,
|
colorScheme: colorScheme,
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
|
fontFamily: inUseFonts?.firstOrNull,
|
||||||
|
fontFamilyFallback: inUseFonts?.sublist(1),
|
||||||
iconTheme: IconThemeData(
|
iconTheme: IconThemeData(
|
||||||
fill: 0,
|
fill: 0,
|
||||||
weight: 400,
|
weight: 400,
|
||||||
@ -52,8 +72,10 @@ Future<ThemeData> createAppTheme(
|
|||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
elevation: hasAppBarTransparent ? 0 : null,
|
elevation: hasAppBarTransparent ? 0 : null,
|
||||||
backgroundColor: hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
|
backgroundColor:
|
||||||
foregroundColor: hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
|
hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
|
||||||
|
foregroundColor:
|
||||||
|
hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
pageTransitionsTheme: PageTransitionsTheme(
|
pageTransitionsTheme: PageTransitionsTheme(
|
||||||
builders: {
|
builders: {
|
||||||
|
@ -10,6 +10,7 @@ class AccountImage extends StatelessWidget {
|
|||||||
final Color? foregroundColor;
|
final Color? foregroundColor;
|
||||||
final double? radius;
|
final double? radius;
|
||||||
final Widget? fallbackWidget;
|
final Widget? fallbackWidget;
|
||||||
|
final Widget? badge;
|
||||||
|
|
||||||
const AccountImage({
|
const AccountImage({
|
||||||
super.key,
|
super.key,
|
||||||
@ -18,6 +19,7 @@ class AccountImage extends StatelessWidget {
|
|||||||
this.foregroundColor,
|
this.foregroundColor,
|
||||||
this.radius,
|
this.radius,
|
||||||
this.fallbackWidget,
|
this.fallbackWidget,
|
||||||
|
this.badge,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -26,7 +28,9 @@ class AccountImage extends StatelessWidget {
|
|||||||
final url = sn.getAttachmentUrl(content ?? '');
|
final url = sn.getAttachmentUrl(content ?? '');
|
||||||
|
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
return CircleAvatar(
|
return Stack(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
key: Key('attachment-${content.hashCode}'),
|
key: Key('attachment-${content.hashCode}'),
|
||||||
radius: radius,
|
radius: radius,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
@ -46,6 +50,14 @@ class AccountImage extends StatelessWidget {
|
|||||||
color: foregroundColor,
|
color: foregroundColor,
|
||||||
))
|
))
|
||||||
: null,
|
: null,
|
||||||
|
),
|
||||||
|
if (badge != null)
|
||||||
|
Positioned(
|
||||||
|
right: -4,
|
||||||
|
bottom: -4,
|
||||||
|
child: badge!,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/screens/account/profile_page.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/account/account_popover.dart';
|
import 'package:surface/widgets/account/account_popover.dart';
|
||||||
@ -105,6 +106,22 @@ class ChatMessage extends StatelessWidget {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: AccountImage(
|
child: AccountImage(
|
||||||
content: user?.avatar,
|
content: user?.avatar,
|
||||||
|
badge: (user?.badges.isNotEmpty ?? false)
|
||||||
|
? Icon(
|
||||||
|
kBadgesMeta[user!.badges.first.type]?.$2 ??
|
||||||
|
Symbols.question_mark,
|
||||||
|
color: kBadgesMeta[user.badges.first.type]?.$3,
|
||||||
|
fill: 1,
|
||||||
|
size: 18,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
blurRadius: 5.0,
|
||||||
|
color: Color.fromARGB(150, 0, 0, 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_highlight/flutter_highlight.dart';
|
||||||
|
import 'package:flutter_highlight/theme_map.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
import 'package:flutter_markdown_latex/flutter_markdown_latex.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:markdown/markdown.dart' as markdown;
|
import 'package:markdown/markdown.dart' as markdown;
|
||||||
@ -72,21 +75,27 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
code: GoogleFonts.robotoMono(height: 1),
|
code: GoogleFonts.robotoMono(height: 1),
|
||||||
),
|
),
|
||||||
builders: {},
|
builders: {
|
||||||
|
'latex': LatexElementBuilder(),
|
||||||
|
'code': HighlightBuilder(),
|
||||||
|
},
|
||||||
softLineBreak: true,
|
softLineBreak: true,
|
||||||
extensionSet: markdown.ExtensionSet(
|
extensionSet: markdown.ExtensionSet(
|
||||||
<markdown.BlockSyntax>[
|
<markdown.BlockSyntax>[
|
||||||
markdown.CodeBlockSyntax(),
|
|
||||||
...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes,
|
...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes,
|
||||||
|
markdown.CodeBlockSyntax(),
|
||||||
|
markdown.FencedCodeBlockSyntax(),
|
||||||
|
LatexBlockSyntax(),
|
||||||
],
|
],
|
||||||
<markdown.InlineSyntax>[
|
<markdown.InlineSyntax>[
|
||||||
|
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes,
|
||||||
if (isAutoWarp) markdown.LineBreakSyntax(),
|
if (isAutoWarp) markdown.LineBreakSyntax(),
|
||||||
_UserNameCardInlineSyntax(),
|
_UserNameCardInlineSyntax(),
|
||||||
_CustomEmoteInlineSyntax(context),
|
_CustomEmoteInlineSyntax(context),
|
||||||
markdown.AutolinkSyntax(),
|
markdown.AutolinkSyntax(),
|
||||||
markdown.AutolinkExtensionSyntax(),
|
markdown.AutolinkExtensionSyntax(),
|
||||||
markdown.CodeSyntax(),
|
markdown.CodeSyntax(),
|
||||||
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes
|
LatexInlineSyntax(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTapLink: (text, href, title) async {
|
onTapLink: (text, href, title) async {
|
||||||
@ -260,3 +269,56 @@ class _CustomEmoteInlineSyntax extends markdown.InlineSyntax {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HighlightBuilder extends MarkdownElementBuilder {
|
||||||
|
@override
|
||||||
|
Widget? visitElementAfterWithContext(
|
||||||
|
BuildContext context,
|
||||||
|
markdown.Element element,
|
||||||
|
TextStyle? preferredStyle,
|
||||||
|
TextStyle? parentStyle,
|
||||||
|
) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
|
||||||
|
if (element.attributes['class'] == null &&
|
||||||
|
!element.textContent.trim().contains('\n')) {
|
||||||
|
return Container(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.only(top: 0.0, right: 4.0, bottom: 1.75, left: 4.0),
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 2.0),
|
||||||
|
color: Colors.black12,
|
||||||
|
child: Text(
|
||||||
|
element.textContent,
|
||||||
|
style: GoogleFonts.robotoMono(textStyle: preferredStyle),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
var language = 'plaintext';
|
||||||
|
final pattern = RegExp(r'^language-(.+)$');
|
||||||
|
if (element.attributes['class'] != null &&
|
||||||
|
pattern.hasMatch(element.attributes['class'] ?? '')) {
|
||||||
|
language =
|
||||||
|
pattern.firstMatch(element.attributes['class'] ?? '')?.group(1) ??
|
||||||
|
'plaintext';
|
||||||
|
}
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: HighlightView(
|
||||||
|
element.textContent.trim(),
|
||||||
|
language: language,
|
||||||
|
theme: {
|
||||||
|
...(isDark ? themeMap['a11y-dark']! : themeMap['a11y-light']!),
|
||||||
|
'root': (isDark
|
||||||
|
? TextStyle(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
color: Color(0xfff8f8f2))
|
||||||
|
: TextStyle(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
color: Color(0xff545454)))
|
||||||
|
},
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
textStyle: GoogleFonts.robotoMono(textStyle: preferredStyle),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -193,7 +193,7 @@ PODS:
|
|||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (~> 3.49.0)
|
- sqlite3 (~> 3.49.1)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
@ -378,7 +378,7 @@ SPEC CHECKSUMS:
|
|||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
||||||
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
|
sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
|
||||||
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||||
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
|
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
|
||||||
|
60
pubspec.lock
60
pubspec.lock
@ -405,10 +405,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dio_web_adapter
|
name: dio_web_adapter
|
||||||
sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
dismissible_page:
|
dismissible_page:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -525,10 +525,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c"
|
sha256: "9467b7c4eedf0bd4c9306b0ec12455b278f6366962be061d0978a446c103c111"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "9.0.1"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -710,6 +710,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
flutter_highlight:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_highlight
|
||||||
|
sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.0"
|
||||||
flutter_inappwebview:
|
flutter_inappwebview:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -803,6 +811,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6+2"
|
version: "0.7.6+2"
|
||||||
|
flutter_markdown_latex:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_markdown_latex
|
||||||
|
sha256: "839e76a84abb3632ffcebbd450cf93c7e9894af65622527d23f0084cee1bfd04"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4"
|
||||||
|
flutter_math_fork:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_math_fork
|
||||||
|
sha256: "284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.3"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -941,6 +965,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
highlight:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: highlight
|
||||||
|
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.0"
|
||||||
home_widget:
|
home_widget:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1393,18 +1425,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35"
|
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.1"
|
version: "8.3.0"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_info_plus_platform_interface
|
name: package_info_plus_platform_interface
|
||||||
sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76"
|
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.2.0"
|
||||||
pasteboard:
|
pasteboard:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1950,10 +1982,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: "57fafacd815c981735406215966ff7caaa8eab984b094f52e692accefcbd9233"
|
sha256: "7adb4cc96dc08648a5eb1d80a7619070796ca6db03901ff2b6dcb15ee30468f3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.30"
|
version: "0.5.31"
|
||||||
sqlparser:
|
sqlparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2050,6 +2082,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.2"
|
version: "0.3.2"
|
||||||
|
tuple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: tuple
|
||||||
|
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -128,6 +128,8 @@ dependencies:
|
|||||||
drift: ^2.25.1
|
drift: ^2.25.1
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.2.4
|
||||||
local_notifier: ^0.1.6
|
local_notifier: ^0.1.6
|
||||||
|
flutter_markdown_latex: ^0.3.4
|
||||||
|
flutter_highlight: ^0.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user