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
2 changes: 1 addition & 1 deletion android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ android {
applicationId = "com.example.anystep"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 21
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
Expand Down
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
4 changes: 2 additions & 2 deletions android/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ pluginManagement {

plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("com.android.application") version "8.9.1" apply false
// START: FlutterFire Configuration
id("com.google.gms.google-services") version("4.3.15") apply false
// END: FlutterFire Configuration
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}

include(":app")
7 changes: 2 additions & 5 deletions lib/core/common/widgets/inputs/address_modal_tile.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:anystep/core/common/constants/spacing.dart';
import 'package:anystep/core/common/widgets/any_step_modal.dart';
import 'package:anystep/core/common/widgets/inputs/any_step_address_form.dart';
import 'package:anystep/core/features/location/presentation/any_step_address_form.dart';
import 'package:anystep/core/features/location/data/address_repository.dart';
import 'package:anystep/core/features/location/domain/address_model.dart';
import 'package:anystep/l10n/generated/app_localizations.dart';
Expand Down Expand Up @@ -275,10 +275,7 @@ class _AddressModalContentState extends ConsumerState<_AddressModalContent> {
style: Theme.of(context).textTheme.titleLarge,
),
),
IconButton(
onPressed: () => context.pop(),
icon: const Icon(Icons.close),
),
IconButton(onPressed: () => context.pop(), icon: const Icon(Icons.close)),
],
),
const SizedBox(height: AnyStepSpacing.sm8),
Expand Down
1 change: 0 additions & 1 deletion lib/core/common/widgets/inputs/inputs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export 'any_step_segment_control.dart';
export 'any_step_text_field.dart';
export 'any_step_date_time_picker.dart';
export 'any_step_switch_input.dart';
export 'any_step_address_form.dart';
export 'address_modal_tile.dart';
export 'address_autocomplete_field.dart';
export 'image_upload_widget.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class _EventFeedScreenState extends ConsumerState<EventFeedScreen> {
if (!mounted) return;

await prefs.setWelcomeMessageSeen();
if (!mounted) return;
context.showModal(
_WelcomeMessageModal(
message: message,
Expand Down Expand Up @@ -97,14 +98,16 @@ class _EventFeedScreenState extends ConsumerState<EventFeedScreen> {
final isAuthenticated = !user.isLoading && user.hasValue && user.value != null;
final isAdmin = isAuthenticated && user.value!.role == UserRole.admin;
final loc = AppLocalizations.of(context);
final currentMonthSummary =
isAuthenticated && !isAdmin ? ref.watch(currentUserHoursSummaryThisMonthProvider) : null;
final currentYtdSummary =
isAuthenticated && !isAdmin ? ref.watch(currentUserHoursSummaryYtdProvider) : null;
final currentMonthlySeries =
isAuthenticated && !isAdmin ? ref.watch(currentUserMonthlyHoursYtdProvider) : null;
final adminMonthSummary =
isAdmin ? ref.watch(volunteerHoursSummaryThisMonthProvider) : null;
final currentMonthSummary = isAuthenticated && !isAdmin
? ref.watch(currentUserHoursSummaryThisMonthProvider)
: null;
final currentYtdSummary = isAuthenticated && !isAdmin
? ref.watch(currentUserHoursSummaryYtdProvider)
: null;
final currentMonthlySeries = isAuthenticated && !isAdmin
? ref.watch(currentUserMonthlyHoursYtdProvider)
: null;
final adminMonthSummary = isAdmin ? ref.watch(volunteerHoursSummaryThisMonthProvider) : null;
final adminYtdSummary = isAdmin ? ref.watch(volunteerHoursSummaryYtdProvider) : null;
final adminMonthlySeries = isAdmin ? ref.watch(volunteerMonthlyHoursYtdProvider) : null;

Expand Down Expand Up @@ -136,9 +139,9 @@ class _EventFeedScreenState extends ConsumerState<EventFeedScreen> {
const SizedBox(width: 8),
Text(loc.login),
],
),
),
),
),
),
),
]
: null,
bottom: PreferredSize(
Expand Down Expand Up @@ -194,7 +197,7 @@ class _EventFeedScreenState extends ConsumerState<EventFeedScreen> {
),
],
),
),
),
),
body: isSearching
? SearchEventsFeed(search: q)
Expand All @@ -214,17 +217,16 @@ class _EventFeedScreenState extends ConsumerState<EventFeedScreen> {
if (isWide) {
slivers.add(
_gridSection(
children: [
metricsCard,
const DashboardCalendarCard(),
],
children: [metricsCard, const DashboardCalendarCard()],
aspectRatio: 0.9,
),
);
} else {
slivers.add(SliverToBoxAdapter(child: metricsCard));
slivers.add(
SliverToBoxAdapter(child: DashboardSectionHeader(title: loc.dashboardCalendar)),
SliverToBoxAdapter(
child: DashboardSectionHeader(title: loc.dashboardCalendar),
),
);
slivers.add(const SliverToBoxAdapter(child: DashboardCalendarCard()));
}
Expand All @@ -241,30 +243,33 @@ class _EventFeedScreenState extends ConsumerState<EventFeedScreen> {
if (isWide) {
slivers.add(
_gridSection(
children: [
metricsCard,
const DashboardCalendarCard(),
],
children: [metricsCard, const DashboardCalendarCard()],
aspectRatio: 0.9,
),
);
} else {
slivers.add(SliverToBoxAdapter(child: metricsCard));
}
slivers.add(
SliverToBoxAdapter(child: DashboardSectionHeader(title: loc.dashboardRecentEvents)),
SliverToBoxAdapter(
child: DashboardSectionHeader(title: loc.dashboardRecentEvents),
),
);
slivers.add(const RecentEventsList(maxItems: 4));
if (!isWide) {
slivers.add(
SliverToBoxAdapter(child: DashboardSectionHeader(title: loc.dashboardCalendar)),
SliverToBoxAdapter(
child: DashboardSectionHeader(title: loc.dashboardCalendar),
),
);
slivers.add(const SliverToBoxAdapter(child: DashboardCalendarCard()));
}
}

slivers.add(
SliverToBoxAdapter(child: DashboardSectionHeader(title: loc.dashboardUpcomingEvents)),
SliverToBoxAdapter(
child: DashboardSectionHeader(title: loc.dashboardUpcomingEvents),
),
);
slivers.add(const UpcomingEventsList());

Expand All @@ -287,10 +292,7 @@ class _EventFeedScreenState extends ConsumerState<EventFeedScreen> {
);
}

SliverPadding _gridSection({
required List<Widget> children,
double aspectRatio = 1.3,
}) {
SliverPadding _gridSection({required List<Widget> children, double aspectRatio = 1.3}) {
return SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: AnyStepSpacing.md16,
Expand Down Expand Up @@ -322,22 +324,13 @@ class _WelcomeMessageModal extends StatelessWidget {
shrinkWrap: true,
padding: const EdgeInsets.all(AnyStepSpacing.md16),
children: [
Text(
loc.welcomeMessageTitle,
style: Theme.of(context).textTheme.titleLarge,
),
Text(loc.welcomeMessageTitle, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: AnyStepSpacing.sm8),
Text(
message,
style: Theme.of(context).textTheme.bodyLarge,
),
Text(message, style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: AnyStepSpacing.md24),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: onDismissed,
child: Text(loc.welcomeMessageDismiss),
),
child: ElevatedButton(onPressed: onDismissed, child: Text(loc.welcomeMessageDismiss)),
),
const SizedBox(height: AnyStepSpacing.sm8),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:anystep/core/common/utils/log_utils.dart';
import 'package:anystep/core/common/utils/snackbar_message.dart';
import 'package:anystep/core/common/utils/state_utils.dart';
import 'package:anystep/core/common/widgets/inputs/any_step_text_field.dart';
import 'package:anystep/core/config/posthog/posthog_manager.dart';
import 'package:anystep/core/features/location/data/address_repository.dart';
import 'package:anystep/core/features/location/data/places_api_client.dart';
import 'package:anystep/core/features/location/domain/address_model.dart';
Expand Down Expand Up @@ -405,12 +406,25 @@ class _AnyStepAddressFormState extends ConsumerState<AnyStepAddressForm> {
final loc = AppLocalizations.of(context);
context.showSuccessSnackbar(loc.addressSaved);
}
PostHogManager.capture(
'address_saved',
properties: <String, Object>{
'is_user_address': widget.isUserAddress,
'has_place_id': (_placeId ?? '').isNotEmpty,
'has_name': (nameValue ?? '').isNotEmpty || (_placeName ?? '').isNotEmpty,
'address_id': saved.id?.toString() ?? '',
},
);
} catch (e, stackTrace) {
Log.e('Error saving address', e, stackTrace);
if (mounted) {
final loc = AppLocalizations.of(context);
context.showErrorSnackbar(loc.addressSaveFailed);
}
PostHogManager.captureException(
PostHogManager.buildPostHogExceptionList(e, stackTrace),
eventName: 'address_save_failed',
);
} finally {
if (mounted) setState(() => _isSaving = false);
}
Expand All @@ -422,14 +436,6 @@ class _AnyStepAddressFormState extends ConsumerState<AnyStepAddressForm> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.showNameField) ...[
AnyStepTextField(
name: widget.nameFieldName,
labelText: widget.nameLabelText ?? "${loc.nameLabel} (${loc.optional})",
validator: widget.nameValidator,
),
const SizedBox(height: AnyStepSpacing.sm4),
],
AnyStepTextField(
name: 'street',
labelText: widget.streetLabelText ?? loc.streetAddress,
Expand Down Expand Up @@ -559,6 +565,14 @@ class _AnyStepAddressFormState extends ConsumerState<AnyStepAddressForm> {
),
],
),
if (widget.showNameField) ...[
const SizedBox(height: AnyStepSpacing.sm4),
AnyStepTextField(
name: widget.nameFieldName,
labelText: widget.nameLabelText ?? "${loc.nameLabel} (${loc.optional})",
validator: widget.nameValidator,
),
],
if (widget.showSaveButton)
Padding(
padding: const EdgeInsets.only(top: AnyStepSpacing.sm8),
Expand Down
30 changes: 28 additions & 2 deletions lib/core/features/notifications/data/notification_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,41 @@ class NotificationRepository {
final user = _supabase.auth.currentUser;
if (user == null) return;
try {
await _supabase.from('users').update({'fcm_token': token}).eq('id', user.id);
Log.i('Synced FCM token for user ${user.id}');
final platform = _platformLabel();
await _supabase.from('user_fcm_tokens').upsert(
{
'user_id': user.id,
'token': token,
'platform': platform,
},
onConflict: 'token',
);
Log.i('Synced FCM token for user ${user.id} ($platform)');
} catch (e, st) {
Log.e('Failed to sync FCM token', e, st);
}
}

}

String _platformLabel() {
if (kIsWeb) return 'web';
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
return 'ios';
case TargetPlatform.android:
return 'android';
case TargetPlatform.macOS:
return 'macos';
case TargetPlatform.windows:
return 'windows';
case TargetPlatform.linux:
return 'linux';
case TargetPlatform.fuchsia:
return 'fuchsia';
}
}

@Riverpod(keepAlive: true)
NotificationRepository notificationRepository(Ref ref) {
final router = ref.watch(routerProvider);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:anystep/core/common/utils/log_utils.dart';
import 'package:anystep/core/features/notifications/data/event_notifications_controller.dart';
import 'package:anystep/core/config/posthog/posthog_manager.dart';
import 'package:anystep/core/common/utils/snackbar_message.dart';
import 'package:anystep/l10n/generated/app_localizations.dart';
import 'package:flutter/material.dart';
Expand All @@ -24,7 +26,16 @@ class EventNotificationsTile extends ConsumerWidget {
await ref.read(eventNotificationsControllerProvider.notifier).setEnabled(value);
if (!context.mounted) return;
context.showSuccessSnackbar(loc.notificationSettingsUpdated);
} catch (_) {
PostHogManager.capture(
'notification_settings_updated',
properties: <String, Object>{'enabled': value},
);
} catch (e, st) {
Log.e('Error updating notification settings', e, st);
PostHogManager.captureException(
PostHogManager.buildPostHogExceptionList(e, st),
eventName: 'notification_settings_update_failed',
);
if (!context.mounted) return;
context.showErrorSnackbar(loc.notificationSettingsUpdateFailed);
}
Expand Down
1 change: 0 additions & 1 deletion lib/core/features/profile/domain/user_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ abstract class UserModel with _$UserModel {
@JsonKey(includeToJson: false, includeFromJson: true, name: "created_at") DateTime? createdAt,
@JsonKey(includeToJson: false, includeFromJson: false) @Default(false) bool isCachedValue,
@JsonKey(name: "agreement_signed_on") DateTime? agreementSignedOn,
@JsonKey(name: "fcm_token") String? fcmToken,
@JsonKey(name: "new_event_notifications_enabled")
@Default(true)
bool newEventNotificationsEnabled,
Expand Down
Loading