Skip to content

Commit 92d12fd

Browse files
authored
Standardised textfield size (#454)
* fix: increase chat input send button size * rename nostrKeysScreen to ProfileKeysScreen * refactor: standardize form field heights and button spacing across UI components * style: format code and improve comments in WnTextFormField component * chore: updated changelog * refactor: improve text field validation and multiline handling with reactive error icon * chore: replace material icons with carbon icons * feat: add conditional border and decoration for chat input when replying * refactor: replace CarbonIcons with Material Icons for arrows and error icons * refactor: replace Material error icons with SVG assets across UI components * feat: replace icon button with SVG arrow up in chat input * Revert "feat: replace icon button with SVG arrow up in chat input" This reverts commit 65345f7.
1 parent 3c1e0c6 commit 92d12fd

File tree

14 files changed

+151
-94
lines changed

14 files changed

+151
-94
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333
- Fixed failing DM group creation in nearly all cases. Note: We're still seeing issues with creating multi-person groups (fix coming soon)
3434
- Fixed blurry splash screen icon on both iOS and Android.
3535
- Fixed absence of border on some contact avatars
36+
- Fixed irregular textfield and button sizes
3637
- Fixed active profile sorting (active profile comes first in account switcher).
3738

3839
## [0.1.2] - 2025-07-15

ios/Podfile.lock

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,20 @@ EXTERNAL SOURCES:
7979
:path: ".symlinks/plugins/sqflite_darwin/darwin"
8080

8181
SPEC CHECKSUMS:
82-
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
83-
emoji_picker_flutter: 8e50ec5caac456a23a78637e02c6293ea0ac8771
82+
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
83+
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
8484
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
85-
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
86-
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
87-
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
88-
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
89-
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
90-
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
91-
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
92-
rust_lib_whitenoise: 69ef24b69b2aba78a7ebabc09a504b5a39177d21
93-
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
94-
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
95-
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
85+
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
86+
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
87+
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
88+
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
89+
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
90+
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
91+
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
92+
rust_lib_whitenoise: 22de658398f8e36a1a396d35b6b6547a0732e6bb
93+
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
94+
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
95+
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
9696

9797
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
9898

lib/routing/router_provider.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import 'package:whitenoise/ui/settings/developer/developer_settings_screen.dart'
1717
import 'package:whitenoise/ui/settings/donate/donate_screen.dart';
1818
import 'package:whitenoise/ui/settings/general_settings_screen.dart';
1919
import 'package:whitenoise/ui/settings/network/network_screen.dart';
20-
import 'package:whitenoise/ui/settings/nostr_keys/nostr_keys_screen.dart';
2120
import 'package:whitenoise/ui/settings/profile/edit_profile_screen.dart';
2221
import 'package:whitenoise/ui/settings/profile/share_profile_qr_scan_screen.dart';
2322
import 'package:whitenoise/ui/settings/profile/share_profile_screen.dart';
23+
import 'package:whitenoise/ui/settings/profile_keys/profile_keys_screen.dart';
2424
import 'package:whitenoise/ui/settings/wallet/wallet_screen.dart';
2525

2626
/// Navigation observer that dismisses toasts when routes change
@@ -172,7 +172,7 @@ final routerProvider = Provider<GoRouter>((ref) {
172172
),
173173
GoRoute(
174174
path: 'keys',
175-
builder: (context, state) => const NostrKeysScreen(),
175+
builder: (context, state) => const ProfileKeysScreen(),
176176
),
177177
GoRoute(
178178
path: 'wallet',

lib/ui/auth_flow/login_screen.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,15 +208,16 @@ class _LoginScreenState extends ConsumerState<LoginScreen> with WidgetsBindingOb
208208
// IntrinsicHeight avoided here since it's been used once in this page already.
209209
// PS this has been tested on different screen sizes and it works fine.
210210
Container(
211-
height: 42.h,
212-
width: 42.h,
211+
height: 56.h,
212+
width: 56.h,
213213
decoration: BoxDecoration(
214214
color: context.colors.avatarSurface,
215215
),
216216
child: CustomIconButton(
217217
iconPath: AssetsPaths.icPaste,
218218
onTap: _pasteFromClipboard,
219-
padding: 12.w,
219+
padding: 20.w,
220+
size: 56.h,
220221
),
221222
),
222223
],

lib/ui/chat/widgets/chat_input.dart

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class _ChatInputState extends ConsumerState<ChatInput> {
8181
_textController.text != chatState.editingMessage[widget.groupId]!.content) {
8282
_textController.text = chatState.editingMessage[widget.groupId]!.content ?? '';
8383
}
84-
84+
final isReplying = chatState.replyingTo[widget.groupId] != null;
8585
return AnimatedPadding(
8686
duration: const Duration(milliseconds: 300),
8787
curve: Curves.easeInOutCubic,
@@ -105,13 +105,16 @@ class _ChatInputState extends ConsumerState<ChatInput> {
105105
child: Container(
106106
decoration: BoxDecoration(
107107
color: context.colors.avatarSurface,
108-
border: Border.all(
109-
color:
110-
_focusNode.hasFocus
111-
? context.colors.primary
112-
: context.colors.input,
113-
width: 1.w,
114-
),
108+
border:
109+
isReplying
110+
? Border.all(
111+
color:
112+
_focusNode.hasFocus
113+
? context.colors.primary
114+
: context.colors.input,
115+
width: 1.w,
116+
)
117+
: null,
115118
),
116119
child: Column(
117120
mainAxisSize: MainAxisSize.min,
@@ -138,11 +141,14 @@ class _ChatInputState extends ConsumerState<ChatInput> {
138141
textInputAction: TextInputAction.newline,
139142
keyboardType: TextInputType.multiline,
140143
textCapitalization: TextCapitalization.sentences,
141-
decoration: const InputDecoration(
142-
border: InputBorder.none,
143-
enabledBorder: InputBorder.none,
144-
focusedBorder: InputBorder.none,
145-
),
144+
decoration:
145+
isReplying
146+
? const InputDecoration(
147+
border: InputBorder.none,
148+
enabledBorder: InputBorder.none,
149+
focusedBorder: InputBorder.none,
150+
)
151+
: null,
146152
),
147153
],
148154
),
@@ -156,13 +162,13 @@ class _ChatInputState extends ConsumerState<ChatInput> {
156162
? Row(
157163
mainAxisSize: MainAxisSize.min,
158164
children: [
159-
Gap(8.w),
165+
Gap(4.w),
160166
WnIconButton(
161167
onPressed: _sendMessage,
162168
icon: Icons.arrow_upward,
163169
backgroundColor: context.colors.primary,
164170
iconColor: context.colors.primaryForeground,
165-
size: 52.w,
171+
size: 56.h,
166172
)
167173
.animate()
168174
.fadeIn(

lib/ui/chat/widgets/stacked_images.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import 'dart:math';
33
import 'package:cached_network_image/cached_network_image.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_screenutil/flutter_screenutil.dart';
6+
import 'package:flutter_svg/svg.dart';
7+
import 'package:whitenoise/ui/core/themes/assets.dart';
68
import 'package:whitenoise/ui/core/themes/src/extensions.dart';
79

810
class StackedImages extends StatelessWidget {
@@ -119,7 +121,15 @@ class StackedImages extends StatelessWidget {
119121
color: Colors.grey[300],
120122
width: size,
121123
height: size,
122-
child: Icon(Icons.error, size: 24.sp),
124+
child: SvgPicture.asset(
125+
AssetsPaths.icErrorFilled,
126+
colorFilter: ColorFilter.mode(
127+
context.colors.destructive,
128+
BlendMode.srcIn,
129+
),
130+
width: 24.w,
131+
height: 24.w,
132+
),
123133
),
124134
)
125135
: Image.file(

lib/ui/contact_list/new_chat_bottom_sheet.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ class _NewChatBottomSheetState extends ConsumerState<NewChatBottomSheet> {
397397
suffixIcon: GestureDetector(
398398
onTap: _scanQRCode,
399399
child: Padding(
400-
padding: EdgeInsets.all(12.w),
400+
padding: EdgeInsets.only(right: 14.w),
401401
child: SvgPicture.asset(
402402
AssetsPaths.icScan,
403403
width: 16.w,

lib/ui/contact_list/start_chat_bottom_sheet.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import 'package:whitenoise/config/providers/contacts_provider.dart';
1111
import 'package:whitenoise/config/providers/group_provider.dart';
1212
import 'package:whitenoise/domain/models/contact_model.dart';
1313
import 'package:whitenoise/domain/services/key_package_service.dart';
14+
import 'package:whitenoise/src/rust/api.dart';
1415
import 'package:whitenoise/src/rust/api/groups.dart';
16+
import 'package:whitenoise/src/rust/api/utils.dart';
1517
import 'package:whitenoise/ui/contact_list/widgets/share_invite_button.dart';
1618
import 'package:whitenoise/ui/contact_list/widgets/share_invite_callout.dart';
1719
import 'package:whitenoise/ui/contact_list/widgets/user_profile.dart';
@@ -84,15 +86,20 @@ class _StartChatBottomSheetState extends ConsumerState<StartChatBottomSheet> {
8486
nip65Relays: activeAccountData.nip65Relays,
8587
);
8688
final keyPackage = await keyPackageService.fetchWithRetry();
87-
8889
if (mounted) {
8990
setState(() {
9091
_isLoadingKeyPackage = false;
9192
_needsInvite = keyPackage == null;
9293
});
9394
}
9495
} catch (e) {
95-
_logger.warning('Failed to fetch key package: $e');
96+
String error;
97+
if (e is WhitenoiseError) {
98+
error = await whitenoiseErrorToString(error: e);
99+
} else {
100+
error = e.toString();
101+
}
102+
_logger.warning('Failed to fetch key package: $error');
96103
if (mounted) {
97104
setState(() {
98105
_isLoadingKeyPackage = false;

lib/ui/core/ui/wn_text_form_field.dart

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter/services.dart';
33
import 'package:flutter_screenutil/flutter_screenutil.dart';
4+
import 'package:flutter_svg/svg.dart';
5+
import 'package:whitenoise/ui/core/themes/assets.dart';
46
import 'package:whitenoise/ui/core/themes/src/extensions.dart';
57
import 'package:whitenoise/ui/core/ui/wn_validation_notification.dart';
68

@@ -9,6 +11,11 @@ enum FieldType {
911
password,
1012
}
1113

14+
enum FieldSize {
15+
regular, // 56.h
16+
small, // 44.h
17+
}
18+
1219
class WnTextFormField extends StatefulWidget {
1320
const WnTextFormField({
1421
super.key,
@@ -42,6 +49,7 @@ class WnTextFormField extends StatefulWidget {
4249
this.textCapitalization = TextCapitalization.none,
4350
this.autovalidateMode = AutovalidateMode.onUserInteraction,
4451
this.inputFormatters,
52+
this.size = FieldSize.regular,
4553
});
4654

4755
final Key? formKey;
@@ -75,6 +83,7 @@ class WnTextFormField extends StatefulWidget {
7583
final TextEditingController? controller;
7684
final FormFieldValidator<String?>? validator;
7785
final InputDecoration? decoration;
86+
final FieldSize size;
7887

7988
@override
8089
State<WnTextFormField> createState() => _WnTextFormFieldState();
@@ -100,21 +109,14 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
100109
});
101110
}
102111

103-
Widget? get suffixIcon {
104-
final resolvedIcon = ValueListenableBuilder(
105-
valueListenable: hasError,
106-
builder: (context, hasError, _) {
107-
final errorIcon = !hasError ? const SizedBox() : const Icon(Icons.error);
108-
109-
return switch (widget.type) {
110-
FieldType.password => errorIcon,
111-
FieldType.standard || null => errorIcon,
112-
};
113-
},
114-
);
115-
116-
return widget.decoration?.suffixIcon ?? resolvedIcon;
117-
}
112+
Widget get suffixIcon => ValueListenableBuilder<bool>(
113+
valueListenable: hasError,
114+
builder:
115+
(_, hasError, _) =>
116+
hasError
117+
? SvgPicture.asset(AssetsPaths.icErrorFilled)
118+
: (widget.decoration?.suffixIcon ?? const SizedBox.shrink()),
119+
);
118120

119121
String? validator(dynamic value) {
120122
final result = widget.validator?.call(value);
@@ -135,7 +137,13 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
135137
fontWeight: FontWeight.w600,
136138
);
137139

140+
final isSmall = widget.size == FieldSize.small;
141+
final targetHeight = isSmall ? 44.h : 56.h;
142+
final bool isMultiline =
143+
((widget.maxLines ?? 1) > 1) || ((widget.minLines ?? 1) > 1) || widget.expands;
144+
138145
final decoration = (widget.decoration ?? const InputDecoration()).copyWith(
146+
constraints: isMultiline ? null : BoxConstraints.tightFor(height: targetHeight),
139147
suffixIcon: suffixIcon,
140148
labelText: widget.labelText,
141149
hintText: widget.hintText,
@@ -146,10 +154,9 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
146154
),
147155
suffixIconColor: context.colors.primary,
148156
fillColor: context.colors.avatarSurface,
149-
contentPadding: EdgeInsets.symmetric(
150-
horizontal: 12.w,
151-
vertical: 16.h,
152-
),
157+
contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: isSmall ? 13.5.h : 19.5.h),
158+
prefixIconConstraints: BoxConstraints.tightFor(height: targetHeight),
159+
suffixIconConstraints: BoxConstraints.tightFor(height: targetHeight),
153160
border: OutlineInputBorder(
154161
borderRadius: BorderRadius.zero,
155162
borderSide: BorderSide(
@@ -165,7 +172,7 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
165172
filled: true,
166173
);
167174

168-
return TextFormField(
175+
final field = TextFormField(
169176
key: widget.formKey,
170177
validator: validator,
171178
enabled: widget.enabled,
@@ -195,5 +202,19 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
195202
keyboardType: widget.keyboardType,
196203
inputFormatters: widget.inputFormatters,
197204
);
205+
206+
// If maxLines or minLines is specified, return the field as is
207+
// Without using ConstainedBox to enforce the target height.
208+
// Same rule applied in InputDecoration above.
209+
if (isMultiline) {
210+
return field;
211+
}
212+
// Also enforce the target height at the parent layout level so surrounding
213+
// widgets measure consistently. The decoration.constraints above ensures
214+
// the border matches this height (no extra whitespace around it).
215+
return ConstrainedBox(
216+
constraints: BoxConstraints.tightFor(height: targetHeight),
217+
child: field,
218+
);
198219
}
199220
}

lib/ui/settings/donate/donate_screen.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ class DonateScreen extends ConsumerWidget {
106106
readOnly: true,
107107
),
108108
),
109-
Gap(8.w),
109+
Gap(4.w),
110110
CustomIconButton(
111111
onTap: () => _copyToClipboard(ref, kLightningAddress),
112112
iconPath: AssetsPaths.icCopy,
113-
size: 56.w,
113+
size: 56.h,
114114
padding: 20.w,
115115
),
116116
],
@@ -135,15 +135,15 @@ class DonateScreen extends ConsumerWidget {
135135
readOnly: true,
136136
),
137137
),
138-
Gap(8.w),
138+
Gap(4.w),
139139
CustomIconButton(
140140
onTap:
141141
() => _copyToClipboard(
142142
ref,
143143
kBitcoinAddress,
144144
),
145145
iconPath: AssetsPaths.icCopy,
146-
size: 56.w,
146+
size: 56.h,
147147
padding: 20.w,
148148
),
149149
],

0 commit comments

Comments
 (0)