Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed failing DM group creation in nearly all cases. Note: We're still seeing issues with creating multi-person groups (fix coming soon)
- Fixed blurry splash screen icon on both iOS and Android.
- Fixed absence of border on some contact avatars
- Fixed irregular textfield and button sizes
- Fixed active profile sorting (active profile comes first in account switcher).

## [0.1.2] - 2025-07-15
Expand Down
26 changes: 13 additions & 13 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,20 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite_darwin/darwin"

SPEC CHECKSUMS:
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
emoji_picker_flutter: 8e50ec5caac456a23a78637e02c6293ea0ac8771
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
rust_lib_whitenoise: 69ef24b69b2aba78a7ebabc09a504b5a39177d21
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
rust_lib_whitenoise: 22de658398f8e36a1a396d35b6b6547a0732e6bb
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0

PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5

Expand Down
4 changes: 2 additions & 2 deletions lib/routing/router_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import 'package:whitenoise/ui/settings/developer/developer_settings_screen.dart'
import 'package:whitenoise/ui/settings/donate/donate_screen.dart';
import 'package:whitenoise/ui/settings/general_settings_screen.dart';
import 'package:whitenoise/ui/settings/network/network_screen.dart';
import 'package:whitenoise/ui/settings/nostr_keys/nostr_keys_screen.dart';
import 'package:whitenoise/ui/settings/profile/edit_profile_screen.dart';
import 'package:whitenoise/ui/settings/profile/share_profile_qr_scan_screen.dart';
import 'package:whitenoise/ui/settings/profile/share_profile_screen.dart';
import 'package:whitenoise/ui/settings/profile_keys/profile_keys_screen.dart';
import 'package:whitenoise/ui/settings/wallet/wallet_screen.dart';

/// Navigation observer that dismisses toasts when routes change
Expand Down Expand Up @@ -172,7 +172,7 @@ final routerProvider = Provider<GoRouter>((ref) {
),
GoRoute(
path: 'keys',
builder: (context, state) => const NostrKeysScreen(),
builder: (context, state) => const ProfileKeysScreen(),
),
GoRoute(
path: 'wallet',
Expand Down
7 changes: 4 additions & 3 deletions lib/ui/auth_flow/login_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,16 @@ class _LoginScreenState extends ConsumerState<LoginScreen> with WidgetsBindingOb
// IntrinsicHeight avoided here since it's been used once in this page already.
// PS this has been tested on different screen sizes and it works fine.
Container(
height: 42.h,
width: 42.h,
height: 56.h,
width: 56.h,
decoration: BoxDecoration(
color: context.colors.avatarSurface,
),
child: CustomIconButton(
iconPath: AssetsPaths.icPaste,
onTap: _pasteFromClipboard,
padding: 12.w,
padding: 20.w,
size: 56.h,
),
),
],
Expand Down
36 changes: 21 additions & 15 deletions lib/ui/chat/widgets/chat_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class _ChatInputState extends ConsumerState<ChatInput> {
_textController.text != chatState.editingMessage[widget.groupId]!.content) {
_textController.text = chatState.editingMessage[widget.groupId]!.content ?? '';
}

final isReplying = chatState.replyingTo[widget.groupId] != null;
return AnimatedPadding(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutCubic,
Expand All @@ -105,13 +105,16 @@ class _ChatInputState extends ConsumerState<ChatInput> {
child: Container(
decoration: BoxDecoration(
color: context.colors.avatarSurface,
border: Border.all(
color:
_focusNode.hasFocus
? context.colors.primary
: context.colors.input,
width: 1.w,
),
border:
isReplying
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I have a question here, cause my eyes are not detecting the difference between master en this branch locally.... 🙈 where is this border difference? I got some screenshots form different cases, in ios emulator.

Replies cases

Case master branch PR branch
Reply input unfocused Master reply before focus PR Reply before focus
Reply input focused without content Master Reply after focus PR Reply after focus
Reply input focused with content Master Reply with content PR Reply with content

"Normal" messages cases (without replies)

Case master branch PR branch
Message input unfocused master-unfocused pr-unfocused
Message input focused without content master-focused pr-focused
Message input focused with content master-content pr-content

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Message input focused with content, that's where the issue is. It's pretty tiny, like a pixel or two but it's pretty noticeable too.

Copy link
Contributor

@josefinalliende josefinalliende Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there the fix is the distance between the input and button right? What it has to do with the border changes? Or there are no border changes really?

? Border.all(
color:
_focusNode.hasFocus
? context.colors.primary
: context.colors.input,
width: 1.w,
)
: null,
),
child: Column(
mainAxisSize: MainAxisSize.min,
Expand All @@ -138,11 +141,14 @@ class _ChatInputState extends ConsumerState<ChatInput> {
textInputAction: TextInputAction.newline,
keyboardType: TextInputType.multiline,
textCapitalization: TextCapitalization.sentences,
decoration: const InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
decoration:
isReplying
? const InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
)
: null,
),
],
),
Expand All @@ -156,13 +162,13 @@ class _ChatInputState extends ConsumerState<ChatInput> {
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Gap(8.w),
Gap(4.w),
WnIconButton(
onPressed: _sendMessage,
icon: Icons.arrow_upward,
backgroundColor: context.colors.primary,
iconColor: context.colors.primaryForeground,
size: 52.w,
size: 56.h,
)
.animate()
.fadeIn(
Expand Down
12 changes: 11 additions & 1 deletion lib/ui/chat/widgets/stacked_images.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:whitenoise/ui/core/themes/assets.dart';
Comment on lines +6 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use the canonical flutter_svg import to avoid build errors

This file imports package:flutter_svg/svg.dart, while the rest of the codebase uses package:flutter_svg/flutter_svg.dart. The non-canonical path can break depending on flutter_svg version. Standardize to the canonical import.

Apply this diff:

-import 'package:flutter_svg/svg.dart';
+import 'package:flutter_svg/flutter_svg.dart';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import 'package:flutter_svg/svg.dart';
import 'package:whitenoise/ui/core/themes/assets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:whitenoise/ui/core/themes/assets.dart';
🤖 Prompt for AI Agents
In lib/ui/chat/widgets/stacked_images.dart around lines 6 to 7, the file imports
flutter_svg using the non-canonical path `package:flutter_svg/svg.dart`; update
that import to the canonical `package:flutter_svg/flutter_svg.dart` (leave the
other import as-is) so it matches the rest of the codebase and avoids
build/version errors, then run the analyzer/build to verify no import errors
remain.

import 'package:whitenoise/ui/core/themes/src/extensions.dart';

class StackedImages extends StatelessWidget {
Expand Down Expand Up @@ -119,7 +121,15 @@ class StackedImages extends StatelessWidget {
color: Colors.grey[300],
width: size,
height: size,
child: Icon(Icons.error, size: 24.sp),
child: SvgPicture.asset(
AssetsPaths.icErrorFilled,
colorFilter: ColorFilter.mode(
context.colors.destructive,
BlendMode.srcIn,
),
width: 24.w,
height: 24.w,
),
),
)
: Image.file(
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/contact_list/new_chat_bottom_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ class _NewChatBottomSheetState extends ConsumerState<NewChatBottomSheet> {
suffixIcon: GestureDetector(
onTap: _scanQRCode,
child: Padding(
padding: EdgeInsets.all(12.w),
padding: EdgeInsets.only(right: 14.w),
child: SvgPicture.asset(
AssetsPaths.icScan,
width: 16.w,
Expand Down
11 changes: 9 additions & 2 deletions lib/ui/contact_list/start_chat_bottom_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import 'package:whitenoise/config/providers/contacts_provider.dart';
import 'package:whitenoise/config/providers/group_provider.dart';
import 'package:whitenoise/domain/models/contact_model.dart';
import 'package:whitenoise/domain/services/key_package_service.dart';
import 'package:whitenoise/src/rust/api.dart';
import 'package:whitenoise/src/rust/api/groups.dart';
import 'package:whitenoise/src/rust/api/utils.dart';
import 'package:whitenoise/ui/contact_list/widgets/share_invite_button.dart';
import 'package:whitenoise/ui/contact_list/widgets/share_invite_callout.dart';
import 'package:whitenoise/ui/contact_list/widgets/user_profile.dart';
Expand Down Expand Up @@ -84,15 +86,20 @@ class _StartChatBottomSheetState extends ConsumerState<StartChatBottomSheet> {
nip65Relays: activeAccountData.nip65Relays,
);
final keyPackage = await keyPackageService.fetchWithRetry();

if (mounted) {
setState(() {
_isLoadingKeyPackage = false;
_needsInvite = keyPackage == null;
});
}
} catch (e) {
_logger.warning('Failed to fetch key package: $e');
String error;
if (e is WhitenoiseError) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is key package service responsibility... it already has a handleFetchKeyPackageError method which could be extended.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know this exists, I'll check it out. Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did it make sense to move it there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. I refactored it and tried to mock the error to make sure I didn't break anything but it's quite tricky. I think I'll just remove it for now and do the refactor if the issue shows up later. It's kinda out of scope for this PR (it's just a side fix) and I don't want to have it delaying this fix. @josefinalliende

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so this if checking the type of error is going to be removed in this PR because it can’t be actually reproduced?

error = await whitenoiseErrorToString(error: e);
} else {
error = e.toString();
}
_logger.warning('Failed to fetch key package: $error');
if (mounted) {
setState(() {
_isLoadingKeyPackage = false;
Expand Down
61 changes: 41 additions & 20 deletions lib/ui/core/ui/wn_text_form_field.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:whitenoise/ui/core/themes/assets.dart';
Comment on lines +4 to +5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use the canonical flutter_svg import to avoid build errors

This file uses package:flutter_svg/svg.dart. Please standardize to package:flutter_svg/flutter_svg.dart like the other files.

Apply this diff:

-import 'package:flutter_svg/svg.dart';
+import 'package:flutter_svg/flutter_svg.dart';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import 'package:flutter_svg/svg.dart';
import 'package:whitenoise/ui/core/themes/assets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:whitenoise/ui/core/themes/assets.dart';
🤖 Prompt for AI Agents
In lib/ui/core/ui/wn_text_form_field.dart around lines 4 to 5, the flutter_svg
import uses the non-canonical path "package:flutter_svg/svg.dart"; change it to
the standard canonical import "package:flutter_svg/flutter_svg.dart" to match
other files and avoid build errors, updating only the import line.

import 'package:whitenoise/ui/core/themes/src/extensions.dart';
import 'package:whitenoise/ui/core/ui/wn_validation_notification.dart';

Expand All @@ -9,6 +11,11 @@ enum FieldType {
password,
}

enum FieldSize {
regular, // 56.h
small, // 44.h
}

class WnTextFormField extends StatefulWidget {
const WnTextFormField({
super.key,
Expand Down Expand Up @@ -42,6 +49,7 @@ class WnTextFormField extends StatefulWidget {
this.textCapitalization = TextCapitalization.none,
this.autovalidateMode = AutovalidateMode.onUserInteraction,
this.inputFormatters,
this.size = FieldSize.regular,
});

final Key? formKey;
Expand Down Expand Up @@ -75,6 +83,7 @@ class WnTextFormField extends StatefulWidget {
final TextEditingController? controller;
final FormFieldValidator<String?>? validator;
final InputDecoration? decoration;
final FieldSize size;

@override
State<WnTextFormField> createState() => _WnTextFormFieldState();
Expand All @@ -100,21 +109,14 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
});
}

Widget? get suffixIcon {
final resolvedIcon = ValueListenableBuilder(
valueListenable: hasError,
builder: (context, hasError, _) {
final errorIcon = !hasError ? const SizedBox() : const Icon(Icons.error);

return switch (widget.type) {
FieldType.password => errorIcon,
FieldType.standard || null => errorIcon,
};
},
);

return widget.decoration?.suffixIcon ?? resolvedIcon;
}
Widget get suffixIcon => ValueListenableBuilder<bool>(
valueListenable: hasError,
builder:
(_, hasError, _) =>
hasError
? SvgPicture.asset(AssetsPaths.icErrorFilled)
: (widget.decoration?.suffixIcon ?? const SizedBox.shrink()),
);

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

final isSmall = widget.size == FieldSize.small;
final targetHeight = isSmall ? 44.h : 56.h;
final bool isMultiline =
((widget.maxLines ?? 1) > 1) || ((widget.minLines ?? 1) > 1) || widget.expands;

final decoration = (widget.decoration ?? const InputDecoration()).copyWith(
constraints: isMultiline ? null : BoxConstraints.tightFor(height: targetHeight),
suffixIcon: suffixIcon,
labelText: widget.labelText,
hintText: widget.hintText,
Expand All @@ -146,10 +154,9 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
),
suffixIconColor: context.colors.primary,
fillColor: context.colors.avatarSurface,
contentPadding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 16.h,
),
contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: isSmall ? 13.5.h : 19.5.h),
prefixIconConstraints: BoxConstraints.tightFor(height: targetHeight),
suffixIconConstraints: BoxConstraints.tightFor(height: targetHeight),
border: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(
Expand All @@ -165,7 +172,7 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
filled: true,
);

return TextFormField(
final field = TextFormField(
key: widget.formKey,
validator: validator,
enabled: widget.enabled,
Expand Down Expand Up @@ -195,5 +202,19 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
keyboardType: widget.keyboardType,
inputFormatters: widget.inputFormatters,
);

// If maxLines or minLines is specified, return the field as is
// Without using ConstainedBox to enforce the target height.
// Same rule applied in InputDecoration above.
if (isMultiline) {
return field;
}
// Also enforce the target height at the parent layout level so surrounding
// widgets measure consistently. The decoration.constraints above ensures
// the border matches this height (no extra whitespace around it).
return ConstrainedBox(
constraints: BoxConstraints.tightFor(height: targetHeight),
child: field,
);
}
}
8 changes: 4 additions & 4 deletions lib/ui/settings/donate/donate_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ class DonateScreen extends ConsumerWidget {
readOnly: true,
),
),
Gap(8.w),
Gap(4.w),
CustomIconButton(
onTap: () => _copyToClipboard(ref, kLightningAddress),
iconPath: AssetsPaths.icCopy,
size: 56.w,
size: 56.h,
padding: 20.w,
),
],
Expand All @@ -135,15 +135,15 @@ class DonateScreen extends ConsumerWidget {
readOnly: true,
),
),
Gap(8.w),
Gap(4.w),
CustomIconButton(
onTap:
() => _copyToClipboard(
ref,
kBitcoinAddress,
),
iconPath: AssetsPaths.icCopy,
size: 56.w,
size: 56.h,
padding: 20.w,
),
],
Expand Down
6 changes: 3 additions & 3 deletions lib/ui/settings/network/add_relay_bottom_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,12 @@ class _AddRelayBottomSheetState extends ConsumerState<AddRelayBottomSheet> {
],
),
),
Gap(8.w),
Gap(4.w),
CustomIconButton(
onTap: _pasteFromClipboard,
iconPath: AssetsPaths.icPaste,
size: 42.h,
padding: 12.w,
size: 56.h,
padding: 20.w,
),
],
),
Expand Down
Loading