1
0
forked from baron/baron-sso

App 카드 크기 조정 및 상세 보기 수정

This commit is contained in:
2026-04-08 14:19:35 +09:00
parent 24f477a28e
commit f4b1c449b1
6 changed files with 251 additions and 87 deletions

View File

@@ -551,6 +551,7 @@ client_id = "Client ID: {{id}}"
client_id_missing = "No client ID available."
current_status = "Current status: {{status}}"
last_auth = "Last signed in: {{value}}"
link_status = "Link status: {{status}}"
link_missing = "This app does not have a launch URL configured."
link_open_error = "Could not open the app link."
render_error = "Dashboard render error: {{error}}"
@@ -2084,7 +2085,8 @@ title = "Cancel consent"
[ui.userfront.dashboard]
last_auth_label = "Last sign-in"
status_history = "Activity history"
link_status_label = "Link status"
status_history = "Link details"
[ui.userfront.dashboard.activity]
linked = "Linked"
@@ -2109,7 +2111,7 @@ confirm_button = "Disconnect"
title = "Disconnect app"
[ui.userfront.dashboard.scopes]
title = "Permission (Scopes)"
title = "Consent scopes"
[ui.userfront.dashboard.status]
revoked = "Revoked"

View File

@@ -192,6 +192,7 @@ client_id = "Client ID: {{id}}"
client_id_missing = "Client ID 없음"
current_status = "현재 상태: {{status}}"
last_auth = "최근 인증: {{value}}"
link_status = "연동 상태: {{status}}"
link_missing = "이동할 페이지 주소(Client URI)가 설정되지 않았습니다."
link_open_error = "해당 링크를 열 수 없습니다."
render_error = "대시보드 렌더링 오류: {{error}}"
@@ -402,7 +403,8 @@ session = "세션"
[ui.userfront.dashboard]
last_auth_label = "최근 인증"
status_history = "상태 이력"
link_status_label = "연동 상태"
status_history = "연동 정보"
[ui.userfront.device]
android = "Mobile(Android)"
@@ -2505,7 +2507,7 @@ confirm_button = "해지하기"
title = "연동 해지"
[ui.userfront.dashboard.scopes]
title = "권한 (Scopes)"
title = "동의 범위"
[ui.userfront.dashboard.status]
revoked = "해지됨"

View File

@@ -86,6 +86,7 @@ client_id = "Client ID: {id}"
client_id_missing = "No client ID available."
current_status = "Current status: {status}"
last_auth = "Last signed in: {value}"
link_status = "Link status: {status}"
link_missing = "This app does not have a launch URL configured."
link_open_error = "Could not open the app link."
render_error = "Dashboard render error: {error}"
@@ -464,7 +465,8 @@ title = "Cancel consent"
[ui.userfront.dashboard]
last_auth_label = "Last sign-in"
status_history = "Activity history"
link_status_label = "Link status"
status_history = "Link details"
[ui.userfront.dashboard.activity]
linked = "Linked"
@@ -489,7 +491,7 @@ confirm_button = "Disconnect"
title = "Disconnect app"
[ui.userfront.dashboard.scopes]
title = "Permission (Scopes)"
title = "Consent scopes"
[ui.userfront.dashboard.status]
revoked = "Revoked"

View File

@@ -62,6 +62,7 @@ client_id = "Client ID: {id}"
client_id_missing = "Client ID 없음"
current_status = "현재 상태: {status}"
last_auth = "최근 인증: {value}"
link_status = "연동 상태: {status}"
link_missing = "이동할 페이지 주소(Client URI)가 설정되지 않았습니다."
link_open_error = "해당 링크를 열 수 없습니다."
render_error = "대시보드 렌더링 오류: {error}"
@@ -176,7 +177,8 @@ session = "세션"
[ui.userfront.dashboard]
last_auth_label = "최근 인증"
status_history = "상태 이력"
link_status_label = "연동 상태"
status_history = "연동 정보"
[ui.userfront.device]
android = "Mobile(Android)"
@@ -694,7 +696,7 @@ confirm_button = "해지하기"
title = "연동 해지"
[ui.userfront.dashboard.scopes]
title = "권한 (Scopes)"
title = "동의 범위"
[ui.userfront.dashboard.status]
revoked = "해지됨"

View File

@@ -38,6 +38,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
static const _border = Color(0xFFE5E7EB);
static const _subtle = Color(0xFFF7F8FA);
static const double _dashboardCardSpacing = 12;
static const double _dashboardCardMaxWidth = 228;
static const double _activityDialogMaxWidth = 360;
static const double _historySessionMinWidth = 92;
static const double _historyOtherColumnsBaselineWidth = 780;
static const int _historySessionMinVisibleChars = 8;
@@ -235,85 +237,158 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
context: context,
builder: (context) => Consumer(
builder: (context, ref, _) {
final dialogWidth = math.min(
MediaQuery.sizeOf(context).width - 48,
_activityDialogMaxWidth,
);
final statusLabel = item.status == 'active'
? tr('ui.userfront.dashboard.activity.linked')
: tr('ui.userfront.dashboard.status.revoked');
final statusColor = _activityStatusColor(item.status);
return AlertDialog(
title: Text(item.appName),
backgroundColor: _surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
insetPadding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 24,
),
contentPadding: const EdgeInsets.fromLTRB(20, 20, 20, 8),
content: SizedBox(
width: double.maxFinite,
width: dialogWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tr('ui.userfront.dashboard.scopes.title'),
style: const TextStyle(fontWeight: FontWeight.bold),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _subtle,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: _border),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.appName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: _ink,
),
),
const SizedBox(height: 4),
Text(
tr('ui.common.details'),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.grey[600],
),
),
],
),
),
const SizedBox(height: 8),
if (item.scopes.isEmpty)
Text(
tr('msg.userfront.dashboard.scopes.empty'),
style: const TextStyle(color: Colors.grey),
)
else
Wrap(
spacing: 8,
runSpacing: 4,
children: item.scopes
.map(
(s) => Chip(
label: Text(
s,
style: const TextStyle(fontSize: 12),
),
visualDensity: VisualDensity.compact,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
const SizedBox(height: 16),
_buildActivityDetailSection(
title: tr('ui.userfront.dashboard.status_history'),
child: Row(
children: [
Expanded(
child: _buildActivityDetailField(
label: tr(
'ui.userfront.dashboard.link_status_label',
),
value: statusLabel,
valueColor: statusColor,
),
),
const SizedBox(width: 10),
Expanded(
child: _buildActivityDetailField(
label: tr('ui.userfront.dashboard.last_auth_label'),
value: item.lastAuthAt,
),
),
],
),
),
const SizedBox(height: 12),
_buildActivityDetailSection(
title: tr('ui.userfront.dashboard.scopes.title'),
child: item.scopes.isEmpty
? Text(
tr('msg.userfront.dashboard.scopes.empty'),
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
)
.toList(),
),
const SizedBox(height: 24),
Text(
tr('ui.userfront.dashboard.status_history'),
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tr(
'msg.userfront.dashboard.last_auth',
params: {'value': item.lastAuthAt},
),
),
const SizedBox(height: 4),
Builder(
builder: (context) {
final statusLabel = item.status == 'active'
? tr('ui.common.status.active')
: tr('ui.userfront.dashboard.status.revoked');
return Text(
tr(
'msg.userfront.dashboard.current_status',
params: {'status': statusLabel},
),
style: TextStyle(
color: item.status == 'active'
? Colors.green
: Colors.grey,
),
);
},
),
],
: Wrap(
spacing: 8,
runSpacing: 8,
children: item.scopes
.map(
(scope) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
decoration: BoxDecoration(
color: _subtle,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _border),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.shield_outlined,
size: 14,
color: _ink,
),
const SizedBox(width: 6),
Text(
scope,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: _ink,
),
),
],
),
),
)
.toList(),
),
),
],
),
),
actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(tr('ui.common.close')),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
foregroundColor: _ink,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
backgroundColor: _subtle,
),
child: Text(
tr('ui.common.close'),
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
),
],
);
@@ -322,6 +397,73 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
);
}
Widget _buildActivityDetailSection({
required String title,
required Widget child,
}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: _surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _border),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: _ink,
),
),
const SizedBox(height: 10),
child,
],
),
);
}
Widget _buildActivityDetailField({
required String label,
required String value,
Color? valueColor,
}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _subtle,
borderRadius: BorderRadius.circular(14),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[600],
),
),
const SizedBox(height: 6),
Text(
value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: valueColor ?? _ink,
),
),
],
),
);
}
Widget _buildSideMenu(BuildContext context, {required bool closeOnTap}) {
return SafeArea(
child: Column(
@@ -1319,7 +1461,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Widget _buildActivityCard(_ActivityItem item, {double? cardWidth}) {
final isActive = item.status == 'active';
final statusColor = isActive ? Colors.green : Colors.grey;
final statusColor = _activityStatusColor(item.status);
final borderColor = isActive
? Colors.green.withValues(alpha: 128)
: _border;
@@ -1331,10 +1473,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
// 카드 컨텐츠
final cardContent = Container(
width: cardWidth ?? 260,
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: _surface,
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: borderColor, width: borderWidth),
boxShadow: isActive
? [
@@ -1355,7 +1497,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
child: Text(
item.appName,
style: const TextStyle(
fontSize: 16,
fontSize: 15,
fontWeight: FontWeight.w600,
color: _ink,
),
@@ -1380,7 +1522,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
),
],
),
const SizedBox(height: 12),
const SizedBox(height: 10),
Text(
tr('ui.userfront.dashboard.last_auth_label'),
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
@@ -1389,12 +1531,12 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Text(
item.lastAuthAt,
style: const TextStyle(
fontSize: 14,
fontSize: 13,
fontWeight: FontWeight.w600,
color: _ink,
),
),
const SizedBox(height: 16),
const SizedBox(height: 14),
Row(
children: [
Expanded(
@@ -1403,7 +1545,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
style: OutlinedButton.styleFrom(
foregroundColor: _ink,
side: const BorderSide(color: _border),
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 7),
),
child: Text(
tr('ui.common.details'),
@@ -1425,7 +1567,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
color: item.isRevoked ? Colors.grey : Colors.redAccent,
width: 0.5,
),
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 7),
),
child: _isRevoking && !item.isRevoked
? const SizedBox(
@@ -1783,8 +1925,15 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
}
double _dashboardCardWidth(double maxWidth, int crossAxisCount) {
return (maxWidth - (_dashboardCardSpacing * (crossAxisCount - 1))) /
crossAxisCount;
return math.min(
(maxWidth - (_dashboardCardSpacing * (crossAxisCount - 1))) /
crossAxisCount,
_dashboardCardMaxWidth,
);
}
Color _activityStatusColor(String status) {
return status == 'active' ? Colors.green : Colors.grey;
}
Widget _buildCenteredHistoryHeader(String label, {double? width}) {

View File

@@ -418,6 +418,7 @@ const Map<String, String> koStrings = {
"msg.userfront.audit.device": "접속환경: {{value}}",
"msg.userfront.audit.end": "더 이상 항목이 없습니다.",
"msg.userfront.audit.filter.description": "활성화된 세션만 보려면 토글을 켜주세요.",
"msg.userfront.audit.filtered_empty": "활성 세션으로 필터링된 접속 이력이 없습니다.",
"msg.userfront.audit.ip": "접속 IP: {{value}}",
"msg.userfront.audit.load_more_error": "더 불러오지 못했습니다.",
"msg.userfront.audit.result": "인증결과: {{value}}",
@@ -460,6 +461,7 @@ const Map<String, String> koStrings = {
"msg.userfront.dashboard.last_auth": "최근 인증: {{value}}",
"msg.userfront.dashboard.link_missing": "이동할 페이지 주소(Client URI)가 설정되지 않았습니다.",
"msg.userfront.dashboard.link_open_error": "해당 링크를 열 수 없습니다.",
"msg.userfront.dashboard.link_status": "연동 상태: {{status}}",
"msg.userfront.dashboard.render_error": "대시보드 렌더링 오류: {{error}}",
"msg.userfront.dashboard.revoke.confirm":
"{{app}} 앱과의 연동을 해지하시겠습니까?\\\\n해지하면 다음 로그인 시 다시 동의가 필요합니다.",
@@ -1717,9 +1719,10 @@ const Map<String, String> koStrings = {
"ui.userfront.dashboard.approved_session.default": "승인한 세션 ID",
"ui.userfront.dashboard.approved_session.userfront": "승인한 Userfront 세션 ID",
"ui.userfront.dashboard.last_auth_label": "최근 인증",
"ui.userfront.dashboard.link_status_label": "연동 상태",
"ui.userfront.dashboard.revoke.confirm_button": "해지하기",
"ui.userfront.dashboard.revoke.title": "연동 해지",
"ui.userfront.dashboard.scopes.title": "권한 (Scopes)",
"ui.userfront.dashboard.scopes.title": "동의 범위",
"ui.userfront.dashboard.sessions.active_badge": "활성화",
"ui.userfront.dashboard.sessions.current_badge": "접속중",
"ui.userfront.dashboard.sessions.current_disabled": "현재 세션",
@@ -2324,6 +2327,8 @@ const Map<String, String> enStrings = {
"msg.userfront.audit.end": "No more items to show.",
"msg.userfront.audit.filter.description":
"Toggle to view only active sessions.",
"msg.userfront.audit.filtered_empty":
"No sign-in history matches the active session filter.",
"msg.userfront.audit.ip": "IP address: {{value}}",
"msg.userfront.audit.load_more_error": "Could not load more history.",
"msg.userfront.audit.result": "Result: {{value}}",
@@ -2376,6 +2381,7 @@ const Map<String, String> enStrings = {
"msg.userfront.dashboard.link_missing":
"This app does not have a launch URL configured.",
"msg.userfront.dashboard.link_open_error": "Could not open the app link.",
"msg.userfront.dashboard.link_status": "Link status: {{status}}",
"msg.userfront.dashboard.render_error": "Dashboard render error: {{error}}",
"msg.userfront.dashboard.revoke.confirm":
"Disconnect {{app}}?\\\\\\\\\\\\\\\\nYou will need to grant access again the next time you sign in.",
@@ -3728,9 +3734,10 @@ const Map<String, String> enStrings = {
"ui.userfront.dashboard.approved_session.userfront":
"Approved UserFront session ID",
"ui.userfront.dashboard.last_auth_label": "Last sign-in",
"ui.userfront.dashboard.link_status_label": "Link status",
"ui.userfront.dashboard.revoke.confirm_button": "Disconnect",
"ui.userfront.dashboard.revoke.title": "Disconnect app",
"ui.userfront.dashboard.scopes.title": "Permission (Scopes)",
"ui.userfront.dashboard.scopes.title": "Consent scopes",
"ui.userfront.dashboard.sessions.active_badge": "Active",
"ui.userfront.dashboard.sessions.current_badge": "Current",
"ui.userfront.dashboard.sessions.current_disabled": "Current session",
@@ -3739,7 +3746,7 @@ const Map<String, String> enStrings = {
"ui.userfront.dashboard.sessions.unknown_device": "Unknown device",
"ui.userfront.dashboard.sessions.unknown_session": "Session",
"ui.userfront.dashboard.status.revoked": "Revoked",
"ui.userfront.dashboard.status_history": "Activity history",
"ui.userfront.dashboard.status_history": "Link details",
"ui.userfront.device.android": "Mobile(Android)",
"ui.userfront.device.ios": "Mobile(iOS)",
"ui.userfront.device.linux": "Desktop(Linux)",