Skip to content

Commit 80421b6

Browse files
feat(cat-voices): scaffold dev tools (#2570)
* feat: scaffold dev tools * chore: TODO about admin panel * chore: create VoicesGestureDetector with common mouse region pattern
1 parent 3cc1ecf commit 80421b6

File tree

35 files changed

+1041
-416
lines changed

35 files changed

+1041
-416
lines changed

catalyst_voices/apps/voices/lib/app/view/app.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ class _AppState extends State<App> {
7777
lazy: false,
7878
create: (_) => Dependencies.instance.get<CampaignStageCubit>(),
7979
),
80+
BlocProvider<DevToolsBloc>(
81+
create: (_) => Dependencies.instance.get<DevToolsBloc>(),
82+
),
8083
];
8184
}
8285
}

catalyst_voices/apps/voices/lib/app/view/app_content.dart

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:catalyst_voices/app/view/app_active_state_listener.dart';
2+
import 'package:catalyst_voices/app/view/app_global_shortcuts.dart';
23
import 'package:catalyst_voices/app/view/app_mobile_access_restriction.dart';
34
import 'package:catalyst_voices/app/view/app_precache_image_assets.dart';
45
import 'package:catalyst_voices/app/view/app_session_listener.dart';
@@ -68,18 +69,20 @@ final class _AppContent extends StatelessWidget {
6869
),
6970
debugShowCheckedModeBanner: false,
7071
builder: (_, child) {
71-
return AppActiveStateListener(
72-
child: AppVideoPrecache(
73-
child: GlobalPrecacheImages(
74-
child: GlobalSessionListener(
75-
// IMPORTANT: AppSplashScreenManager must be placed above all
76-
// widgets that render visible UI elements. Any widget that
77-
// displays content should be a descendant of
78-
// AppSplashScreenManager to ensure proper splash
79-
// screen behavior.
80-
child: AppSplashScreenManager(
81-
child: AppMobileAccessRestriction(
82-
child: child ?? const SizedBox.shrink(),
72+
return AppGlobalShortcuts(
73+
child: AppActiveStateListener(
74+
child: AppVideoPrecache(
75+
child: GlobalPrecacheImages(
76+
child: GlobalSessionListener(
77+
// IMPORTANT: AppSplashScreenManager must be placed above all
78+
// widgets that render visible UI elements. Any widget that
79+
// displays content should be a descendant of
80+
// AppSplashScreenManager to ensure proper splash
81+
// screen behavior.
82+
child: AppSplashScreenManager(
83+
child: AppMobileAccessRestriction(
84+
child: child ?? const SizedBox.shrink(),
85+
),
8386
),
8487
),
8588
),
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import 'dart:async';
2+
3+
import 'package:catalyst_voices/pages/dev_tools/dev_tools_page.dart';
4+
import 'package:catalyst_voices/routes/routes.dart';
5+
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter/services.dart';
8+
9+
class AppGlobalShortcuts extends StatelessWidget {
10+
final Widget child;
11+
12+
const AppGlobalShortcuts({
13+
super.key,
14+
required this.child,
15+
});
16+
17+
@override
18+
Widget build(BuildContext context) {
19+
final isDeveloper = context.select<DevToolsBloc, bool>((value) => value.state.isDeveloper);
20+
21+
// TODO(damian-molinski): migrate AdminTools shortcut here from SpacesShellPage.
22+
return CallbackShortcuts(
23+
bindings: {
24+
if (isDeveloper)
25+
LogicalKeySet(
26+
LogicalKeyboardKey.meta,
27+
LogicalKeyboardKey.shift,
28+
LogicalKeyboardKey.keyD,
29+
): () {
30+
final routerContext = AppRouter.rootNavigatorKey.currentContext;
31+
if (routerContext != null) {
32+
unawaited(DevToolsPage.show(routerContext));
33+
}
34+
},
35+
},
36+
child: child,
37+
);
38+
}
39+
}

catalyst_voices/apps/voices/lib/dependency/dependencies.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,14 @@ final class Dependencies extends DependencyProvider {
148148
);
149149
})
150150
..registerFactory<CampaignStageCubit>(() {
151-
return CampaignStageCubit(get<CampaignService>());
151+
return CampaignStageCubit(
152+
get<CampaignService>(),
153+
);
154+
})
155+
..registerFactory<DevToolsBloc>(() {
156+
return DevToolsBloc(
157+
get<DevToolsService>(),
158+
);
152159
});
153160
}
154161

@@ -221,6 +228,13 @@ final class Dependencies extends DependencyProvider {
221228
get<SignedDocumentManager>(),
222229
get<DocumentRepository>(),
223230
),
231+
)
232+
..registerLazySingleton<DevToolsRepository>(
233+
() {
234+
return DevToolsRepository(
235+
get<DevToolsStorage>(),
236+
);
237+
},
224238
);
225239
}
226240

@@ -317,6 +331,11 @@ final class Dependencies extends DependencyProvider {
317331
get<DocumentRepository>(),
318332
);
319333
});
334+
registerLazySingleton<DevToolsService>(() {
335+
return DevToolsService(
336+
get<DevToolsRepository>(),
337+
);
338+
});
320339
}
321340

322341
void _registerStorages() {
@@ -344,6 +363,11 @@ final class Dependencies extends DependencyProvider {
344363
sharedPreferences: get<SharedPreferencesAsync>(),
345364
);
346365
});
366+
registerLazySingleton<DevToolsStorage>(() {
367+
return DevToolsStorageLocal(
368+
sharedPreferences: get<SharedPreferencesAsync>(),
369+
);
370+
});
347371
}
348372

349373
void _registerUtils() {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:catalyst_voices/widgets/widgets.dart';
2+
import 'package:flutter/material.dart';
3+
4+
class DevToolsPage extends StatefulWidget {
5+
const DevToolsPage._();
6+
7+
@override
8+
State<DevToolsPage> createState() => _DevToolsPageState();
9+
10+
static Future<void> show(BuildContext context) {
11+
final route = PageRouteBuilder<void>(
12+
pageBuilder: (context, animation, secondaryAnimation) {
13+
return FadeTransition(
14+
opacity: animation,
15+
child: const DevToolsPage._(),
16+
);
17+
},
18+
transitionDuration: const Duration(milliseconds: 200),
19+
settings: const RouteSettings(name: '/dev-tools'),
20+
);
21+
22+
return Navigator.push(context, route);
23+
}
24+
}
25+
26+
class _DevToolsPageState extends State<DevToolsPage> {
27+
@override
28+
Widget build(BuildContext context) {
29+
return const Scaffold(
30+
appBar: VoicesAppBar(),
31+
body: SizedBox.expand(child: Placeholder(child: Center(child: Text('DevTools')))),
32+
);
33+
}
34+
}

catalyst_voices/apps/voices/lib/pages/discovery/sections/most_recent_proposals.dart

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import 'dart:async';
22

33
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
44
import 'package:catalyst_voices/routes/routes.dart';
5-
import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart';
65
import 'package:catalyst_voices/widgets/cards/proposal/pending_proposal_card.dart';
76
import 'package:catalyst_voices/widgets/scrollbar/voices_slider.dart';
7+
import 'package:catalyst_voices/widgets/widgets.dart';
88
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
99
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
1010
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
@@ -104,50 +104,47 @@ class _LatestProposalsState extends State<MostRecentProposals> {
104104
),
105105
),
106106
const SizedBox(height: 48),
107-
MouseRegion(
108-
cursor: SystemMouseCursors.click,
109-
child: GestureDetector(
110-
onHorizontalDragUpdate: _onHorizontalDrag,
111-
child: SizedBox(
112-
height: 440,
113-
width: 1200,
114-
child: Center(
115-
child: ListView.separated(
116-
controller: _scrollController,
117-
shrinkWrap: true,
118-
physics: const ClampingScrollPhysics(),
119-
scrollDirection: Axis.horizontal,
120-
itemCount: widget.proposals.length,
121-
itemBuilder: (context, index) {
122-
final proposal = widget.proposals[index];
123-
final ref = proposal.ref;
124-
return Skeletonizer(
125-
enabled: widget.isLoading,
126-
child: PendingProposalCard(
127-
key: Key('PendingProposalCard_$ref'),
128-
proposal: proposal,
129-
onTap: () {
130-
unawaited(
131-
ProposalRoute(
132-
proposalId: ref.id,
133-
version: ref.version,
134-
).push(context),
135-
);
136-
},
137-
onFavoriteChanged: (value) async {
138-
final bloc = context.read<DiscoveryCubit>();
139-
if (value) {
140-
await bloc.addFavorite(ref);
141-
} else {
142-
await bloc.removeFavorite(ref);
143-
}
144-
},
145-
isFavorite: proposal.isFavorite,
146-
),
147-
);
148-
},
149-
separatorBuilder: (context, index) => const SizedBox(width: 24),
150-
),
107+
VoicesGestureDetector(
108+
onHorizontalDragUpdate: _onHorizontalDrag,
109+
child: SizedBox(
110+
height: 440,
111+
width: 1200,
112+
child: Center(
113+
child: ListView.separated(
114+
controller: _scrollController,
115+
shrinkWrap: true,
116+
physics: const ClampingScrollPhysics(),
117+
scrollDirection: Axis.horizontal,
118+
itemCount: widget.proposals.length,
119+
itemBuilder: (context, index) {
120+
final proposal = widget.proposals[index];
121+
final ref = proposal.ref;
122+
return Skeletonizer(
123+
enabled: widget.isLoading,
124+
child: PendingProposalCard(
125+
key: Key('PendingProposalCard_$ref'),
126+
proposal: proposal,
127+
onTap: () {
128+
unawaited(
129+
ProposalRoute(
130+
proposalId: ref.id,
131+
version: ref.version,
132+
).push(context),
133+
);
134+
},
135+
onFavoriteChanged: (value) async {
136+
final bloc = context.read<DiscoveryCubit>();
137+
if (value) {
138+
await bloc.addFavorite(ref);
139+
} else {
140+
await bloc.removeFavorite(ref);
141+
}
142+
},
143+
isFavorite: proposal.isFavorite,
144+
),
145+
);
146+
},
147+
separatorBuilder: (context, index) => const SizedBox(width: 24),
151148
),
152149
),
153150
),

catalyst_voices/apps/voices/lib/pages/workspace/header/workspace_header.dart

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,51 +30,48 @@ class _HasCommentCard extends StatelessWidget {
3030

3131
@override
3232
Widget build(BuildContext context) {
33-
return MouseRegion(
34-
cursor: SystemMouseCursors.click,
35-
child: GestureDetector(
36-
onTap: () async => ProposalsRoute.myProposals().push(context),
37-
child: Container(
38-
decoration: BoxDecoration(
39-
color: context.colors.elevationsOnSurfaceNeutralLv0,
40-
borderRadius: BorderRadius.circular(16),
41-
),
42-
padding: const EdgeInsets.all(16),
43-
constraints: const BoxConstraints(
44-
maxWidth: 300,
45-
maxHeight: 190,
46-
),
47-
child: Column(
48-
crossAxisAlignment: CrossAxisAlignment.start,
49-
mainAxisSize: MainAxisSize.min,
50-
children: [
51-
Row(
52-
children: [
53-
VoicesAssets.icons.chat.buildIcon(
54-
color: context.colors.iconsPrimary,
55-
),
56-
const Spacer(),
57-
VoicesAssets.icons.arrowRight.buildIcon(
58-
color: context.colors.primaryContainer,
59-
),
60-
],
61-
),
62-
const Spacer(),
63-
Text(
64-
context.l10n.viewProposalComments,
65-
style: context.textTheme.titleSmall?.copyWith(
66-
color: context.colors.textOnPrimaryLevel1,
33+
return VoicesGestureDetector(
34+
onTap: () async => ProposalsRoute.myProposals().push(context),
35+
child: Container(
36+
decoration: BoxDecoration(
37+
color: context.colors.elevationsOnSurfaceNeutralLv0,
38+
borderRadius: BorderRadius.circular(16),
39+
),
40+
padding: const EdgeInsets.all(16),
41+
constraints: const BoxConstraints(
42+
maxWidth: 300,
43+
maxHeight: 190,
44+
),
45+
child: Column(
46+
crossAxisAlignment: CrossAxisAlignment.start,
47+
mainAxisSize: MainAxisSize.min,
48+
children: [
49+
Row(
50+
children: [
51+
VoicesAssets.icons.chat.buildIcon(
52+
color: context.colors.iconsPrimary,
6753
),
68-
),
69-
Text(
70-
context.l10n.viewProposalCommentsDescription,
71-
style: context.textTheme.bodyMedium?.copyWith(
72-
color: context.colors.sysColorsNeutralN60,
54+
const Spacer(),
55+
VoicesAssets.icons.arrowRight.buildIcon(
56+
color: context.colors.primaryContainer,
7357
),
58+
],
59+
),
60+
const Spacer(),
61+
Text(
62+
context.l10n.viewProposalComments,
63+
style: context.textTheme.titleSmall?.copyWith(
64+
color: context.colors.textOnPrimaryLevel1,
7465
),
75-
const Spacer(),
76-
],
77-
),
66+
),
67+
Text(
68+
context.l10n.viewProposalCommentsDescription,
69+
style: context.textTheme.bodyMedium?.copyWith(
70+
color: context.colors.sysColorsNeutralN60,
71+
),
72+
),
73+
const Spacer(),
74+
],
7875
),
7976
),
8077
);

0 commit comments

Comments
 (0)