From c349ab018393801b3ee79a5d185d4411fe35a113 Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Thu, 4 Jan 2024 23:27:30 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=A7=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=AE?= =?UTF-8?q?=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3=E3=82=92=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/extensions/string_extensions.dart | 42 +++++++++++++++++ lib/model/color_theme.dart | 32 ++----------- lib/view/user_page/user_detail.dart | 68 ++++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 35 deletions(-) diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 38701b214..6cc0b07dd 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -6,4 +6,46 @@ extension StringExtensions on String { .replaceAll(Characters(''), Characters('\u{200B}')) .toString(); } + + Color? toColor() { + final code = startsWith("#") ? substring(1) : this; + switch (code.length) { + case 3: + final rgb = code + .split("") + .map((c) => int.tryParse(c, radix: 16)) + .nonNulls + .map((i) => i * 16 + i) + .toList(); + if (rgb.length == 3) { + return Color.fromRGBO(rgb[0], rgb[1], rgb[2], 1); + } + case 4: + final argb = code + .split("") + .map((c) => int.tryParse(c, radix: 16)) + .nonNulls + .map((i) => i * 16 + i) + .toList(); + if (argb.length != 4) { + return Color.fromARGB(argb[0], argb[1], argb[2], argb[3]); + } + case 6: + final hex = int.tryParse(code, radix: 16); + if (hex != null) { + return Color(hex + 0xFF000000); + } + case 8: + final hex = int.tryParse( + "${code.substring(6)}${code.substring(0, 6)}", + radix: 16, + ); + if (hex != null) { + return Color(hex); + } + default: + return null; + } + return null; + } } diff --git a/lib/model/color_theme.dart b/lib/model/color_theme.dart index a9b895212..9a1dd919b 100644 --- a/lib/model/color_theme.dart +++ b/lib/model/color_theme.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:miria/extensions/color_extension.dart'; +import 'package:miria/extensions/string_extensions.dart'; import 'package:miria/model/misskey_theme.dart'; part 'color_theme.freezed.dart'; @@ -80,34 +81,9 @@ class ColorTheme with _$ColorTheme { return Color.fromRGBO(rgb[0], rgb[1], rgb[2], opacity); } - final code = (input.startsWith("#") ? input.substring(1) : input); - - if (code.length == 3) { - final rgb = code - .split("") - .map((c) => int.parse(c, radix: 16)) - .map((i) => i * 16 + i) - .toList(); - return Color.fromRGBO(rgb[0], rgb[1], rgb[2], 1); - } - if (code.length == 4) { - final argb = code - .split("") - .map((c) => int.parse(c, radix: 16)) - .map((i) => i * 16 + i) - .toList(); - return Color.fromARGB(argb[0], argb[1], argb[2], argb[3]); - } - if (code.length == 6) { - return Color(int.parse(code, radix: 16) + 0xFF000000); - } - if (code.length == 8) { - return Color( - int.parse( - "${code.substring(6)}${code.substring(0, 6)}", - radix: 16, - ), - ); + final color = input.toColor(); + if (color != null) { + return color; } throw FormatException("invalid color format", val); diff --git a/lib/view/user_page/user_detail.dart b/lib/view/user_page/user_detail.dart index 858602e51..6aafa5fa0 100644 --- a/lib/view/user_page/user_detail.dart +++ b/lib/view/user_page/user_detail.dart @@ -3,6 +3,7 @@ import 'package:confetti/confetti.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/extensions/date_time_extension.dart'; +import 'package:miria/extensions/string_extensions.dart'; import 'package:miria/model/account.dart'; import 'package:miria/providers.dart'; import 'package:miria/router/app_router.dart'; @@ -12,6 +13,7 @@ import 'package:miria/view/common/constants.dart'; import 'package:miria/view/common/error_dialog_handler.dart'; import 'package:miria/view/common/misskey_notes/mfm_text.dart'; import 'package:miria/view/common/misskey_notes/misskey_note.dart'; +import 'package:miria/view/common/misskey_notes/network_image.dart'; import 'package:miria/view/dialogs/simple_confirm_dialog.dart'; import 'package:miria/view/themes/app_theme.dart'; import 'package:miria/view/user_page/update_memo_dialog.dart'; @@ -357,13 +359,7 @@ class UserDetailState extends ConsumerState { spacing: 5, runSpacing: 5, children: [ - for (final role in response.roles ?? []) - Container( - decoration: BoxDecoration( - border: - Border.all(color: Theme.of(context).dividerColor)), - padding: const EdgeInsets.all(5), - child: Text(role.name)), + for (final role in response.roles ?? []) RoleChip(role: role), ], ), const Padding(padding: EdgeInsets.only(top: 5)), @@ -590,6 +586,64 @@ class BirthdayConfettiState extends State { } } +class RoleChip extends ConsumerWidget { + const RoleChip({super.key, required this.role}); + + final UserRole role; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final account = AccountScope.of(context); + final textStyle = Theme.of(context).textTheme.bodyMedium; + final height = MediaQuery.textScalerOf(context) + .scale((textStyle?.fontSize ?? 14) * (textStyle?.height ?? 1)); + return Tooltip( + message: role.description, + child: GestureDetector( + onTap: () async { + final response = await ref + .read(misskeyProvider(account)) + .roles + .show(RolesShowRequest(roleId: role.id)); + if (response.isPublic && response.isExplorable) { + if (!context.mounted) return; + context.pushRoute( + ExploreRoleUsersRoute(item: response, account: account), + ); + } + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: role.color?.toColor() ?? Theme.of(context).dividerColor, + ), + borderRadius: BorderRadius.circular(height), + ), + padding: const EdgeInsets.symmetric( + vertical: 5, + horizontal: 10, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (role.iconUrl != null) + Padding( + padding: const EdgeInsets.only(right: 5), + child: NetworkImageView( + url: role.iconUrl!.toString(), + type: ImageType.role, + height: height, + ), + ), + Text(role.name), + ], + ), + ), + ), + ); + } +} + extension on UsersShowResponse { bool get requiresFollowRequest { return isLocked && From fcde9c7fd7158769fbcf42927797b37c498a88b3 Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Fri, 5 Jan 2024 01:03:56 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=AE?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4=E3=83=B3=E3=82=BF?= =?UTF-8?q?=E3=83=96=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../explore_page/explore_role_users_page.dart | 82 ++++++++++++++----- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/lib/view/explore_page/explore_role_users_page.dart b/lib/view/explore_page/explore_role_users_page.dart index 485261b3c..6a17b320b 100644 --- a/lib/view/explore_page/explore_role_users_page.dart +++ b/lib/view/explore_page/explore_role_users_page.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; import 'package:miria/providers.dart'; import 'package:miria/view/common/account_scope.dart'; +import 'package:miria/view/common/misskey_notes/misskey_note.dart'; import 'package:miria/view/common/pushable_listview.dart'; import 'package:miria/view/user_page/user_list_item.dart'; import 'package:misskey_dart/misskey_dart.dart'; @@ -21,27 +22,66 @@ class ExploreRoleUsersPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return AccountScope( - account: account, - child: Scaffold( - appBar: AppBar(title: Text(item.name)), - body: PushableListView( - initializeFuture: () async { - final response = await ref - .read(misskeyProvider(account)) - .roles - .users(RolesUsersRequest(roleId: item.id)); - return response.toList(); - }, - nextFuture: (lastItem, _) async { - final response = await ref - .read(misskeyProvider(account)) - .roles - .users( - RolesUsersRequest(roleId: item.id, untilId: lastItem.id)); - return response.toList(); - }, - itemBuilder: (context, item) => UserListItem(user: item.user), + return DefaultTabController( + length: 2, + child: AccountScope( + account: account, + child: Scaffold( + appBar: AppBar( + title: Text(item.name), + bottom: const TabBar( + tabs: [ + Tab(text: "ユーザー"), + Tab(text: "タイムライン"), + ], + ), + ), + body: TabBarView( + children: [ + PushableListView( + initializeFuture: () async { + final response = await ref + .read(misskeyProvider(account)) + .roles + .users(RolesUsersRequest(roleId: item.id)); + return response.toList(); + }, + nextFuture: (lastItem, _) async { + final response = + await ref.read(misskeyProvider(account)).roles.users( + RolesUsersRequest( + roleId: item.id, + untilId: lastItem.id, + ), + ); + return response.toList(); + }, + itemBuilder: (context, item) => UserListItem(user: item.user), + ), + PushableListView( + initializeFuture: () async { + final response = await ref + .read(misskeyProvider(account)) + .roles + .notes(RolesNotesRequest(roleId: item.id)); + ref.read(notesProvider(account)).registerAll(response); + return response.toList(); + }, + nextFuture: (lastItem, _) async { + final response = + await ref.read(misskeyProvider(account)).roles.notes( + RolesNotesRequest( + roleId: item.id, + untilId: lastItem.id, + ), + ); + ref.read(notesProvider(account)).registerAll(response); + return response.toList(); + }, + itemBuilder: (context, note) => MisskeyNote(note: note), + ), + ], + ), ), ), );