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
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
.swiftpm/
migrate_working_dir/
.env
.env.local
.env.development
.env.production
.env.*
.envrc
*.env
!.env.example
!.env.*.example

# IntelliJ related
*.iml
Expand Down
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
emoji_picker_flutter: 8e50ec5caac456a23a78637e02c6293ea0ac8771
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
Expand Down
33 changes: 31 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ precommit:
just test-rust
@echo "✅ All pre-commit checks passed!"

# Pre-commit checks without auto-fixing (for releases)
precommit-check:
just deps-flutter
just deps-rust
just check-rust-format
just check-dart-format
just lint
just test-flutter
just test-rust
@echo "✅ All pre-commit checks passed!"

# ==============================================================================
# CODE GENERATION
# ==============================================================================
Expand Down Expand Up @@ -203,9 +214,27 @@ android-build:
# Check and build versioned release
release:
@echo "🔨 Building versioned release..."
@echo "🔍 Verifying working tree is clean..."
@if [ -n "$$(git status --porcelain)" ]; then \
echo "❌ Working tree is not clean. Please commit or stash changes before release."; \
git status --short; \
exit 1; \
fi
@echo "✅ Working tree is clean"
@echo "🔍 Verifying build script..."
@if [ ! -f "scripts/build.sh" ]; then \
echo "❌ Build script not found: scripts/build.sh"; \
exit 1; \
fi
@if [ ! -x "scripts/build.sh" ]; then \
echo "❌ Build script is not executable: scripts/build.sh"; \
echo "💡 Run: chmod +x scripts/build.sh"; \
exit 1; \
fi
@echo "✅ Build script verified"
@echo "✔︎ Running a precommit check..."
just precommit
@echo "🎁 Building versioned release for all platforms..."
just precommit-check
@echo "🎁 Building versioned release for Android and iOS..."
./scripts/build.sh --full --versioned
@echo "🎉 Versioned release built successfully!"

Expand Down
1 change: 1 addition & 0 deletions lib/config/providers/create_profile_screen_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class CreateProfileScreenNotifier extends Notifier<CreateProfileScreenState> {

if (activeAccount == null) {
ref.showRawErrorToast('No active account found');
state = state.copyWith(isLoading: false);
return;
}

Expand Down
30 changes: 12 additions & 18 deletions lib/config/providers/edit_profile_screen_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ class EditProfileScreenNotifier extends AsyncNotifier<ProfileState> {
_logger.severe('loadProfileData', e, st);
state = AsyncValue.error(e.toString(), st);
} finally {
state = AsyncValue.data(
state.value!.copyWith(isSaving: false, stackTrace: null, error: null),
);
final stateValue = state.value;
if (stateValue != null) {
state = AsyncValue.data(
stateValue.copyWith(isSaving: false, stackTrace: null, error: null),
);
}
}
}

Expand Down Expand Up @@ -111,9 +114,9 @@ class EditProfileScreenNotifier extends AsyncNotifier<ProfileState> {
}

Future<void> updateProfileData() async {
state = AsyncValue.data(
state.value!.copyWith(isSaving: true, error: null, stackTrace: null),
);
final previousState = state.asData?.value ?? const ProfileState();
state = AsyncValue.data(previousState.copyWith(isSaving: true, error: null, stackTrace: null));

try {
String? profilePictureUrl;
final authState = ref.read(authProvider);
Expand Down Expand Up @@ -169,18 +172,9 @@ class EditProfileScreenNotifier extends AsyncNotifier<ProfileState> {
await fetchProfileData();
} catch (e, st) {
_logger.severe('updateProfileData', e, st);
state = AsyncValue.data(
state.value!.copyWith(isSaving: false, error: e, stackTrace: st),
);

// Handle error messaging
String? errorMessage;
if (e is ApiError) {
errorMessage = await e.messageText();
} else {
errorMessage = e.toString();
}
state = AsyncValue.error(errorMessage, st);
final prev = state.asData?.value ?? const ProfileState();
final message = e is ApiError ? (await e.messageText()) : e.toString();
state = AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));
}
Comment on lines +175 to 178
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Standardize error handling: keep errors inside ProfileState everywhere and add explicit types.

This block correctly retains form data by storing the error in ProfileState instead of flipping to AsyncValue.error. Apply this pattern consistently (e.g., authentication/active-account failures and image-pick failure currently use AsyncValue.error) to avoid UI state loss and inconsistency. Also add explicit types.

-      final prev = state.asData?.value ?? const ProfileState();
-      final message = e is ApiError ? (await e.messageText()) : e.toString();
+      final ProfileState prev = state.asData?.value ?? const ProfileState();
+      final String message = e is ApiError ? (await e.messageText()) : e.toString();
       state = AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));

Example helper you can add to this notifier to replace all current AsyncValue.error(...) sites:

void _setLocalError(String message, StackTrace st) {
  final ProfileState prev = state.asData?.value ?? const ProfileState();
  state = AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));
}

Then replace, for example:

  • Lines 25–29 (“Not authenticated”)
  • Lines 37–38 (“No active account found”)
  • Line 54 (catch in fetchProfileData)
  • Lines 112–113 (image pick failure)
  • Lines 124–125, 133–135 (auth/active account in updateProfileData)

with _setLocalError(...) to keep the UI data intact.

🤖 Prompt for AI Agents
In lib/config/providers/edit_profile_screen_provider.dart around lines 175–178,
error handling flips to AsyncValue.error in other places which loses UI form
data and lacks explicit types; add a typed helper like _setLocalError(String
message, StackTrace st) that reads the previous ProfileState (final ProfileState
prev = state.asData?.value ?? const ProfileState()) and sets state =
AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));
then replace all current AsyncValue.error(...) and untyped error assignments at
the listed locations (lines 25–29 “Not authenticated”, 37–38 “No active account
found”, the catch at line 54 in fetchProfileData, lines 112–113 image pick
failure, and lines 124–125 and 133–135 in updateProfileData) with calls to
_setLocalError(message, st) using explicit String and StackTrace types so errors
remain inside ProfileState and form data is preserved.

}
}
Expand Down
9 changes: 5 additions & 4 deletions lib/config/providers/follows_provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// ignore_for_file: avoid_redundant_argument_values

import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:whitenoise/config/providers/active_account_provider.dart';
Expand Down Expand Up @@ -42,16 +41,16 @@ class FollowsNotifier extends Notifier<FollowsState> {
FollowsState build() {
ref.listen<String?>(activePubkeyProvider, (previous, next) {
if (previous != null && next != null && previous != next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.microtask(() {
clearFollows();
loadFollows();
});
} else if (previous != null && next == null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.microtask(() {
clearFollows();
});
} else if (previous == null && next != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.microtask(() {
loadFollows();
});
}
Expand All @@ -70,6 +69,8 @@ class FollowsNotifier extends Notifier<FollowsState> {
}

Future<void> loadFollows() async {
if (state.isLoading) return;

state = state.copyWith(isLoading: true, error: null);

if (!_isAuthAvailable()) {
Expand Down
2 changes: 1 addition & 1 deletion lib/config/providers/metadata_cache_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class MetadataCacheNotifier extends Notifier<MetadataCacheState> {
final activeAccountState = await ref.read(activeAccountProvider.future);
final activeAccount = activeAccountState.account;
if (activeAccount == null) {
throw 'No active account found';
throw StateError('No active account found');
}

final metadata = await wn_users_api.userMetadata(pubkey: fetchKey);
Expand Down
20 changes: 10 additions & 10 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -739,26 +739,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
version: "11.0.1"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.9"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
Expand Down Expand Up @@ -1247,10 +1247,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
version: "0.7.4"
version: "0.7.6"
timing:
dependency: transitive
description:
Expand Down Expand Up @@ -1343,10 +1343,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
vm_service:
dependency: transitive
description:
Expand Down