diff --git a/.cursor/rules/flutter.mdc b/.cursor/rules/flutter.mdc deleted file mode 100644 index d4ebc100..00000000 --- a/.cursor/rules/flutter.mdc +++ /dev/null @@ -1,201 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- -You are a senior Dart programmer with experience in the Flutter framework. - -Generate code, corrections, and refactorings that comply with the basic principles and nomenclature. - -## Dart General Guidelines - -### Basic Principles - -- Use English for all code and documentation. -- Always declare the type of each variable and function (parameters and return value). - - Avoid using any. - - Create necessary types. -- Don't leave blank lines within a function. -- One export per file. - -### Nomenclature - -- Use PascalCase for classes. -- Use camelCase for variables, functions, and methods. -- Use underscores_case for file and directory names. -- Use UPPERCASE for environment variables. - - Avoid magic numbers and define constants. -- Start each function with a verb. -- Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc. -- Use complete words instead of abbreviations and correct spelling. - - Except for standard abbreviations like API, URL, etc. - - Except for well-known abbreviations: - - i, j for loops - - err for errors - - ctx for contexts - - req, res, next for middleware function parameters - -### Functions - -- In this context, what is understood as a function will also apply to a method. -- Write short functions with a single purpose. Less than 20 instructions. -- Name functions with a verb and something else. - - If it returns a boolean, use isX or hasX, canX, etc. - - If it doesn't return anything, use executeX or saveX, etc. -- Avoid nesting blocks by: - - Early checks and returns. - - Extraction to utility functions. -- Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting. - - Use arrow functions for simple functions (less than 3 instructions). - - Use named functions for non-simple functions. -- Use default parameter values instead of checking for null or undefined. -- Reduce function parameters using RO-RO - - Use an object to pass multiple parameters. - - Use an object to return results. - - Declare necessary types for input arguments and output. -- Use a single level of abstraction. - -### Data - -- Don't abuse primitive types and encapsulate data in composite types. -- Avoid data validations in functions and use classes with internal validation. -- Prefer immutability for data. - - Use readonly for data that doesn't change. - - Use as const for literals that don't change. - -### Classes - -- Follow SOLID principles. -- Prefer composition over inheritance. -- Declare interfaces to define contracts. -- Write small classes with a single purpose. - - Less than 200 instructions. - - Less than 10 public methods. - - Less than 10 properties. - -### Exceptions - -- Use exceptions to handle errors you don't expect. -- If you catch an exception, it should be to: - - Fix an expected problem. - - Add context. - - Otherwise, use a global handler. - -## Specific to Flutter - -### Basic Principles - -- Use the following architecture structure: - ``` - lib/ - ├── main.dart # App entry point - └── src/ - ├── sprk_app.dart # Main MaterialApp - ├── core/ # Shared code across features - │ ├── config/ # Application-wide configurations - │ ├── di/ # Dependency injection setup - │ ├── network/ # ATProto client, API base - │ ├── routing/ # AutoRoute setup - │ ├── storage/ # Local storage utilities - │ │ ├── cache/ # Cache management - │ │ └── preferences/ # Settings and preferences - │ ├── theme/ # Theme definitions - │ ├── auth/ # Authentication system - │ │ └── data/ - │ │ └── repositories/ - │ ├── feed_algorithms/ # Feed algorithms - │ ├── l10n/ # Localization (when implemented) - │ ├── widgets/ # Common widgets - │ └── utils/ # Shared utilities - │ └── logging/ # Logging framework - └── features/ # Feature modules - └── feature/ # (auth, feed, profile, etc.) - ├── data/ # Data layer for this feature - │ ├── repositories/ - │ └── models/ - ├── providers/ # Riverpod providers - └── ui/ # UI components - ├── pages/ - └── widgets/ - ``` - - Folder structure explanation: - - `lib/src/core/`: Contains all shared code that's used across multiple features. - - `config/`: Holds application-wide configurations, such as environment settings, API keys, or feature flags. - - `di/`: Manages dependency injection setup using GetIt to register and resolve dependencies. - - `network/`: Handles all network-related functionality, including the ATProto client, API base classes, and repositories. Use only methods included in the ATProto API. - - `routing/`: Contains the AutoRoute setup, including route definitions, guards, and navigation helpers. - - `storage/`: Contains utilities for local storage. - - `cache/`: Cache management with SQL-based implementation, download manager, and cache interfaces. - - `preferences/`: Settings, storage manager, secure storage, and shared preferences. - - `theme/`: Defines the application's themes, including colors, typography, and component styles for different modes (e.g., light/dark). - - `auth/`: Centralized authentication system with repositories for auth operations. - - `feed_algorithms/`: Contains feed algorithms and related logic. - - `l10n/`: Manages localization and internationalization (when implemented). - - `widgets/`: Houses reusable UI components that are used across multiple features. - - `utils/`: Includes utility functions and helpers. - - `logging/`: Complete logging framework with different log levels, outputs, and predefined loggers. - - - `lib/src/features/`: Contains feature-specific modules, where each feature is a self-contained unit. - - Current features: `auth`, `feed`, `profile`, `splash`, `settings`, `comments`, `search`, `messages`, `home` - - Each feature follows the same structure: - - `data/`: Handles all data-related concerns for the feature. - - `providers/`: Contains Riverpod providers that manage the state and business logic. - - `ui/`: Contains all UI components with `pages/` and `widgets/` subdirectories. - -- The backend is using AT Protocol. Any interfacing between frontend and backend will use the AT Protocol API -- Use repository pattern for data persistence - - Use the cache manager: `CacheManagerInterface` from GetIt - - Use SQL cache: `SQLCacheInterface` from GetIt - - Use download manager: `DownloadManagerInterface` from GetIt -- Use Riverpod to manage state - - see keepAlive if you need to keep the state alive - - use riverpod annotations -- Use freezed to manage UI states -- Use GetIt to manage dependencies - - Use singleton for services and repositories - - Use factory for use cases - - Use lazy singleton for controllers -- Use AutoRoute to manage routes - - Use extras to pass data between pages -- Use extensions to manage reusable code -- Use ThemeData to manage themes -- Use AppLocalizations to manage translations (when implemented) -- Use constants to manage constants values -- Use `StorageManager` from GetIt to manage storage -- When a widget tree becomes too deep, it can lead to longer build times and increased memory usage. Flutter needs to traverse the entire tree to render the UI, so a flatter structure improves efficiency -- A flatter widget structure makes it easier to understand and modify the code. Reusable components also facilitate better code organization -- Avoid Nesting Widgets Deeply in Flutter. Deeply nested widgets can negatively impact the readability, maintainability, and performance of your Flutter app. Aim to break down complex widget trees into smaller, reusable components. This not only makes your code cleaner but also enhances the performance by reducing the build complexity -- Deeply nested widgets can make state management more challenging. By keeping the tree shallow, it becomes easier to manage state and pass data between widgets -- Break down large widgets into smaller, focused widgets -- Utilize const constructors wherever possible to reduce rebuilds -- Use Flutter 3.x features. -- Always use withAlpha instead of withOpacity -- Never use relative paths in imports -- Register pages in `lib/src/core/routing/pages.dart` and routes in `lib/src/core/routing/app_router.dart` -- Avoid functions that return widgets -- Use `lib/src/core/utils/logging/` for logging - get `LogService` from GetIt -- Storage keys are always stored at `lib/src/core/storage/preferences/storage_constants.dart` -- Repositories should always have an interface and an implementation (thing_repository.dart, thing_repository_impl.dart) -- For storage, get a `StorageManager` from GetIt -- For cache, get a `CacheManagerInterface` from GetIt -- For auth, get an `AuthRepository` from GetIt -- For API calls, check if `lib/src/core/network/data/repositories/sprk_repository.dart` already has that method in its contract - - If it does, get `SprkRepository` from GetIt -- Available repositories in service locator: - - `LogService` - Logging service - - `SQLCacheInterface` - SQL-based cache - - `CacheManagerInterface` - Cache manager - - `DownloadManagerInterface` - Download manager - - `StorageManager` - Storage management - - `AuthRepository` - Authentication - - `SprkRepository` - Main API repository - - `IdentityRepository` - User identity - - `ThemeRepository` - Theme management - - `ActorRepository` - Actor operations - - `GraphRepository` - Graph operations - - `SettingsRepository` - Settings management - - `OnboardingRepository` - Onboarding flow -- Don't forget to add new repositories to the service locator at `lib/src/core/di/service_locator.dart` - - \ No newline at end of file diff --git a/.cursor/rules/logging.mdc b/.cursor/rules/logging.mdc deleted file mode 100644 index 7d8c4b11..00000000 --- a/.cursor/rules/logging.mdc +++ /dev/null @@ -1,61 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- -# Spark Logging Framework - -A lightweight, flexible logging system for Spark Social with multiple outputs and log levels. - -## Features -- 6 log levels: verbose, debug, info, warning, error, fatal -- Console and file outputs -- Automatic stack traces -- Color-coded console logs -- File rotation - -## Basic Usage - -```dart - -import 'package:spark/src/core/utils/logging/logger.dart'; - -// Get a logger -final logger = GetIt.instance().getLogger('MyFeature'); - -// Log at different levels -logger.v('Verbose message'); -logger.d('Debug message'); -logger.i('Info message'); -logger.w('Warning message'); -logger.e('Error message', error: exception, stackTrace: stackTrace); -logger.f('Fatal message'); -``` - -## Predefined Loggers -```dart -final logService = GetIt.instance(); - -// Use predefined loggers -logService.appLogger.i('Application message'); -logService.networkLogger.d('Network operation'); -logService.uiLogger.w('UI warning'); -``` - -## Configure Log Levels - -```dart -// Set minimum log level -LoggerFactory.setGlobalLogLevel(LogLevel.warning); // production -LoggerFactory.setGlobalLogLevel(LogLevel.debug); // development -``` - -## Log Levels - -- **VERBOSE**: Detailed debugging information -- **DEBUG**: Debugging information -- **INFO**: General application flow -- **WARNING**: Potential issues -- **ERROR**: Errors that impact functionality -- **FATAL**: Critical errors -- **NOTHING**: Special level to suppress logs diff --git a/.cursor/rules/project-overview.mdc b/.cursor/rules/project-overview.mdc deleted file mode 100644 index c689a866..00000000 --- a/.cursor/rules/project-overview.mdc +++ /dev/null @@ -1,107 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- -# Spark Social - Project Overview - -This is a Flutter application for a social media platform inspired by TikTok, integrated with the AT Protocol. - -## Main Entry Points -- [lib/main.dart](mdc:lib/main.dart) - Main application entry point -- [lib/src/sprk_app.dart](mdc:lib/src/sprk_app.dart) - Main MaterialApp configuration -- [pubspec.yaml](mdc:pubspec.yaml) - Project dependencies and configuration - -## Key Architecture Files -- [lib/src/core/di/service_locator.dart](mdc:lib/src/core/di/service_locator.dart) - Dependency injection setup -- [lib/src/core/routing/app_router.dart](mdc:lib/src/core/routing/app_router.dart) - AutoRoute configuration -- [lib/src/core/storage/preferences/storage_manager.dart](mdc:lib/src/core/storage/preferences/storage_manager.dart) - Storage management -- [lib/src/core/utils/logging/](mdc:lib/src/core/utils/logging) - Logging framework - -## Current Features -- **Authentication** (`lib/src/features/auth/`) - User login/logout with AT Protocol -- **Feed** (`lib/src/features/feed/`) - Main social media feed with posts and videos -- **Profile** (`lib/src/features/profile/`) - User profiles and profile management -- **Comments** (`lib/src/features/comments/`) - Comment system for posts -- **Search** (`lib/src/features/search/`) - Search functionality -- **Messages** (`lib/src/features/messages/`) - Direct messaging -- **Settings** (`lib/src/features/settings/`) - App settings and preferences -- **Home** (`lib/src/features/home/`) - Home navigation -- **Splash** (`lib/src/features/splash/`) - App initialization screen - -## Project Structure -``` - lib/ - ├── main.dart # App entry point - └── src/ - ├── sprk_app.dart # Main MaterialApp - ├── core/ # Shared code across features - │ ├── config/ # Application-wide configurations - │ ├── di/ # Dependency injection setup (GetIt) - │ ├── network/ # ATProto client, API repositories - │ │ └── data/ - │ │ ├── models/ - │ │ └── repositories/ - │ ├── routing/ # AutoRoute setup - │ ├── storage/ # Local storage utilities - │ │ ├── cache/ # SQL-based cache system - │ │ └── preferences/ # Settings and secure storage - │ ├── theme/ # Theme system with repositories - │ │ ├── data/ - │ │ │ ├── models/ - │ │ │ └── repositories/ - │ │ └── domain/ - │ ├── auth/ # Centralized authentication - │ │ └── data/ - │ │ └── repositories/ - │ ├── feed_algorithms/ # Feed algorithms - │ ├── widgets/ # Common reusable widgets - │ └── utils/ # Shared utilities - │ └── logging/ # Complete logging framework - └── features/ # Feature modules - ├── auth/ # Authentication UI and logic - ├── feed/ # Feed display and interactions - ├── profile/ # User profiles - ├── comments/ # Comment system - ├── search/ # Search functionality - ├── messages/ # Direct messaging - ├── settings/ # App settings - ├── home/ # Home navigation - └── splash/ # App initialization -``` - -## Tech Stack -- **Flutter 3.7+** with Dart -- **AT Protocol** for backend communication -- **Riverpod** for state management -- **AutoRoute** for navigation -- **GetIt** for dependency injection -- **Freezed** for immutable data classes -- **SQLite** for local caching -- **Secure Storage** for sensitive data -- **Shared Preferences** for app settings - -## Key Dependencies -- `atproto: ^0.13.3` - AT Protocol client -- `bluesky: ^0.18.10` - Bluesky integration -- `flutter_riverpod: ^2.4.9` - State management -- `auto_route: ^10.1.0+1` - Navigation -- `get_it: ^7.6.7` - Dependency injection -- `sqflite: ^2.4.2` - SQL database -- `flutter_secure_storage: ^9.2.4` - Secure storage -- `cached_network_image: ^3.3.1` - Image caching -- `video_player: ^2.9.3` - Video playback - -## Available Services (GetIt) -- `LogService` - Comprehensive logging system -- `StorageManager` - Local storage management -- `SQLCacheInterface` - SQL-based caching -- `CacheManagerInterface` - File cache management -- `DownloadManagerInterface` - Download operations -- `AuthRepository` - Authentication services -- `SprkRepository` - Main API operations -- `ThemeRepository` - Theme management -- `SettingsRepository` - App settings -- `ActorRepository` - User actor operations -- `GraphRepository` - Social graph operations -- `OnboardingRepository` - User onboarding diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..5bd2d0e0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# AGENTS.md + +## Project at a glance +- Root Flutter app: `spark` +- Workspace members: `widgetbook`, `fonts`, `assets` +- Stack: feature-first + Riverpod + GetIt + Freezed + AutoRoute +- Generated files in use: `*.g.dart`, `*.freezed.dart`, `*.gr.dart` + +## Setup +1. Use Flutter `3.38.4` (stable, CI-aligned) +2. Ensure `.env` exists: `touch .env` +3. Install deps: `flutter pub get --enforce-lockfile` + +## Common commands (repo root) +- Deps: `flutter pub get --enforce-lockfile` +- Codegen: `dart run build_runner build --delete-conflicting-outputs` +- Format: `dart format .` +- Format check: `dart format --set-exit-if-changed .` +- Analyze all: `flutter analyze .` +- Run app: `flutter run` + +## Code conventions +- Prefer `package:spark/...` imports; avoid deep cross-feature relative imports +- Import order: Dart SDK, third-party, project; keep `part` after imports +- Use strong explicit types; avoid `dynamic` unless required at boundaries +- Use Freezed for immutable models and `@riverpod` for providers +- Model async state consistently with `AsyncValue` +- Naming: types `PascalCase`, members/providers `lowerCamelCase`, private `_name` +- Keep feature flow: external/API/storage -> repository -> provider -> widget +- Use GetIt (`GetIt.I` / `sl`) for DI-managed services +- Never hand-edit generated files; regenerate instead + +## Reliability and logging +- Wrap fallible async work in `try/catch` +- After `await`: check `mounted` in widgets, `ref.mounted` in providers +- Prefer graceful failures over crashes (`AsyncValue.error`, typed/null fallback) +- Use `LogService` / `SparkLogger`, not `print` +- Log context + stack traces; use proper levels (`d`, `i`, `w`, `e`, `f`) + +## Agent workflow +1. Read nearby feature files for local patterns +2. Edit source files; run codegen when annotations/models change +3. Format touched code (`dart format .`) +4. Analyze (`flutter analyze lib`, or `flutter analyze .` for wider impact) +5. Run targeted tests first, then broader tests +6. Keep comments minimal and only when needed + +## References +- `analysis_options.yaml` +- `lib/src/features/README.md` +- `lib/src/core/utils/logging/README.md` +- `.github/workflows/flutter_lint.yml` +- `.github/workflows/android-internal-release.yml` + +## Safety +- Never commit secrets (`.env`, platform credentials) +- Do not revert unrelated local changes +- Keep diffs scoped to the feature/task diff --git a/README.md b/README.md index 171ce1e8..e5d0659d 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,128 @@ -# Spark Social +# Spark Client -A decentralized social network for video sharing built on the AT Protocol, putting users in control of their digital presence and data. +Flutter client for Spark social. This repository contains the production mobile app, +plus workspace packages used by the app (assets, fonts, and widgetbook). -![Spark Logo](https://static.sprk.so/branding/logo-horizontal-t6.png) +## What This Repo Contains -## Spark Your Creativity +- `spark` app package at repo root (`pubspec.yaml`) +- Flutter workspace members: + - `widgetbook` (component/dev preview package) + - `fonts` (shared font package) + - `assets` (shared assets package) -Share videos freely while maintaining complete control over your data. Break free from corporate control and gain digital autonomy. +The app is organized with a feature-first structure and uses Riverpod + GetIt + +Freezed + AutoRoute. -## About Spark +## Tech Stack -We are building a decentralized social network on the AT Protocol, empowering users to share content without compromising privacy or control. With Spark, you own your data and decide how it's used. - -## Core Principles - -- **Decentralized Network**: Built on the AT Protocol, giving you full control over your digital presence -- **User-First Approach**: Your data belongs to you, share content freely without compromising privacy -- **Digital Autonomy**: Break free from corporate control and take charge of your online experience - -## Features - -- **Content Filters**: Customize your feed with advanced content filters -- **Moderation Lists**: Create and subscribe to moderation lists for a healthier online environment -- **Custom Feeds**: Build personalized feeds based on your interests and favorite creators -- **Music & Audio Gallery**: Platform for musicians to reach wider audiences and listeners to discover new talent -- **Built-in Video Editor**: Create and edit professional-quality videos directly in the app -- **Creative Effects**: Share your creativity with Spark effects or design your own -- **Full Content Control**: You decide what to share and with whom -- **Social Media Detox**: Tools to reduce social media addiction and improve focus -- **Community Building**: Connect with like-minded individuals and build genuine communities -- **Human-first Discovery**: Find real creators and build genuine connections - -## What Makes Spark Different? - -- **Authenticity**: Rediscover genuine connections with real creators -- **Decentralization**: Break free from corporate control and gain digital autonomy -- **Custom Lexicon**: Our own lexicon provides more flexibility for content creators -- **Higher Content Limits**: Increased limits for video length and image quality -- **User Control**: You own your data and decide how it's used -- **Community Focus**: Build meaningful relationships in a supportive environment +- Flutter / Dart +- Riverpod (with code generation) +- GetIt for dependency injection +- Freezed + json_serializable for immutable models +- AutoRoute for navigation +- AT Protocol client libraries (`atproto`, `bluesky`) -## Screenshots +## Prerequisites -(Screenshots coming soon) +- Flutter SDK (CI uses stable `3.38.4`) +- Dart SDK matching Flutter toolchain +- Xcode (for iOS builds) and/or Android SDK -## Getting Started +## Quick Start -### Prerequisites +From repository root: -- Flutter SDK -- Dart SDK -- iOS/Android development environment +```bash +touch .env +flutter pub get --enforce-lockfile +dart run build_runner build --delete-conflicting-outputs +flutter run +``` -### Installation +## Common Commands -1. Clone this repository +### Dependencies and codegen ```bash -git clone https://github.com/sprksocial/spark-front-end.git +flutter pub get --enforce-lockfile +dart run build_runner build --delete-conflicting-outputs +dart run build_runner watch --delete-conflicting-outputs ``` -2. Navigate to the project directory +### Lint and format ```bash -cd spark-front-end +flutter analyze lib +flutter analyze . +dart format . +dart format --set-exit-if-changed . ``` -3. Install dependencies +### Tests + +No tests are currently committed, but these are the standard commands: ```bash -flutter pub get +flutter test +flutter test test/path/to/some_test.dart +flutter test test/path/to/some_test.dart --plain-name "does something specific" ``` -4. Generate code +For `widgetbook` (run inside `widgetbook/`): ```bash -dart run build_runner build --delete-conflicting-outputs +flutter test ``` -5. Run the app +### Builds ```bash -flutter run +flutter build appbundle +flutter build apk +flutter build ios --no-codesign ``` -## Technologies Used +## Project Layout + +```text +lib/ + main.dart + src/ + core/ # shared infrastructure (network, routing, utils, theme, etc.) + features/ # feature modules + / + data/ + providers/ + ui/ +widgetbook/ # widgetbook workspace package +fonts/ # local font package +assets/ # local assets package +``` -- Flutter -- AT Protocol for decentralized social networking -- Cupertino (iOS-style) widgets -- Riverpod for state management -- AutoRoute for routing -- GetIt for dependency injection -- Freezed for data models -- Logger for logging -- Ionicons for beautiful icons -- Video player for media playback -- Camera for video recording -- Animation for smooth transitions +## Architecture Notes -## Project Structure +- Prefer package imports (`package:spark/...`) for app code. +- Typical flow is: external/API/storage -> repository -> provider -> widget. +- Providers are generated with `@riverpod`; immutable state is typically Freezed. +- Generated files (`*.g.dart`, `*.freezed.dart`, `*.gr.dart`) should not be edited manually. -``` - lib/ - ├── main.dart # App entry point - └── src/ - ├── sprk_app.dart # Main MaterialApp - ├── core/ # Shared code across features - │ ├── config/ # Application-wide configurations - │ ├── di/ # Dependency injection setup - │ ├── network/ # ATProto client, API base - │ ├── routing/ # AutoRoute setup - │ ├── storage/ # Local storage utilities - │ ├── theme/ # Theme definitions - │ ├── l10n/ # Localization - │ ├── widgets/ # Common widgets - │ └── utils/ # Shared utilities - └── features/ # Feature modules - └── feature/ - ├── data/ # Data layer for this feature - │ ├── repositories/ - │ └── models/ - ├── providers/ # Riverpod providers - └── ui/ # UI components - ├── pages/ - └── widgets/ -``` +## CI Overview + +- Lint workflow runs codegen, then `flutter analyze`. +- Android internal release workflow runs codegen, config setup, then `flutter build appbundle`. -## Future Enhancements +See: -- Enhanced data portability -- Custom server hosting options -- Advanced content creation tools -- Cross-platform federation -- Community moderation tools -- Expanded creative effects library +- `.github/workflows/flutter_lint.yml` +- `.github/workflows/android-internal-release.yml` ## Contributing -Contributions are welcome! Feel free to submit a Pull Request. +1. Keep changes scoped to the feature you are editing. +2. Run format, codegen (if needed), and analyze before opening a PR. +3. Do not commit secrets (`.env`, signing keys, service credentials). ## License -This project is licensed under the MIT License - see the LICENSE file for details. - -## Connect With Us - -- [Subscribe to Newsletter](https://spark-social-link-to-newsletter.com) -- [Learn More](https://spark-social-learn-more.com) +MIT. See `LICENSE`. diff --git a/lib/main.dart b/lib/main.dart index d89797ec..fa9b7312 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -48,16 +47,7 @@ void main() async { /// Setup logging framework based on environment void _setupLogging() { - final logService = sl(); - - // Set log level based on debug mode - if (kDebugMode) { - logService.setGlobalLogLevel(LogLevel.debug); - logService.appLogger.i('Debug logging enabled'); - } else { - logService.setGlobalLogLevel(LogLevel.info); - logService.appLogger.i('Production logging enabled'); - } + sl().setGlobalLogLevel(LogLevel.warning); } /// Initialize auth repository and wait for it to be ready @@ -69,9 +59,7 @@ Future _initializeAuth() async { final authRepository = sl(); if (authRepository is AuthRepositoryImpl) { - logger.d('Waiting for AuthRepository initialization...'); await authRepository.initializationComplete; - logger.d('AuthRepository initialized successfully'); } } catch (e) { logger.e('AuthRepository initialization failed', error: e); diff --git a/lib/src/core/auth/data/repositories/auth_repository_impl.dart b/lib/src/core/auth/data/repositories/auth_repository_impl.dart index 4d3e29ed..052f2565 100644 --- a/lib/src/core/auth/data/repositories/auth_repository_impl.dart +++ b/lib/src/core/auth/data/repositories/auth_repository_impl.dart @@ -24,7 +24,6 @@ OAuthClientMetadata? _cachedClientMetadata; /// Implementation of the authentication repository for AT Protocol using OAuth class AuthRepositoryImpl implements AuthRepository { AuthRepositoryImpl() { - _logger.i('Initializing AuthRepository'); _initialize(); } @@ -87,11 +86,9 @@ class AuthRepositoryImpl implements AuthRepository { /// Fetches a DID document, handling both did:plc and did:web methods. Future> _fetchDidDocument(String did) async { final url = DidUtils.buildDidDocumentUrl(did); - _logger.d('Fetching DID document from: $url'); final response = await http.get(url); if (response.statusCode != 200) { - _logger.e('Failed to fetch DID document: ${response.statusCode}'); throw Exception('Failed to fetch DID document: ${response.statusCode}'); } @@ -118,15 +115,12 @@ class AuthRepositoryImpl implements AuthRepository { Future _loadSavedSession() async { try { - _logger.d('Loading saved account'); - // Load account as single JSON object - much faster than multiple reads final accountJson = await StorageManager.instance.secure.getString( StorageKeys.account, ); if (accountJson == null) { - _logger.d('No saved account found'); return; } @@ -160,10 +154,8 @@ class AuthRepositoryImpl implements AuthRepository { if (tokenNeedsRefresh && _oauthServer != null) { final metadata = await _getCachedClientMetadata(); _oauthClient = OAuthClient(metadata, service: _oauthServer!); - _logger.d('Access token expired or expiring soon, refreshing'); final refreshed = await refreshToken(); if (!refreshed) { - _logger.w('Token refresh failed during restore, clearing session'); await _clearSavedSession(); _oauthSession = null; _atProto = null; @@ -174,7 +166,6 @@ class AuthRepositoryImpl implements AuthRepository { _oauthClient = null; return; } - _logger.i('Token refreshed successfully during restore'); } // Extract just the host from the PDS endpoint @@ -186,8 +177,6 @@ class AuthRepositoryImpl implements AuthRepository { _oauthSession!, service: pdsHost, ); - - _logger.i('Account loaded successfully for user: $_handle'); } catch (e) { _logger.e('Error loading saved account', error: e); } @@ -197,8 +186,6 @@ class AuthRepositoryImpl implements AuthRepository { if (_oauthSession == null) return; try { - _logger.d('Saving account for user: $_handle'); - final account = Account( accessToken: _oauthSession!.accessToken, refreshToken: _oauthSession!.refreshToken, @@ -216,8 +203,6 @@ class AuthRepositoryImpl implements AuthRepository { StorageKeys.account, account.toJsonString(), ); - - _logger.d('Account saved successfully'); } catch (e) { _logger.e('Failed to save account', error: e); } @@ -225,14 +210,12 @@ class AuthRepositoryImpl implements AuthRepository { Future _clearSavedSession() async { try { - _logger.d('Clearing saved account'); await StorageManager.instance.secure.remove(StorageKeys.account); await StorageManager.instance.secure.remove( StorageKeys.pendingAuthContext, ); // Also clear old session format if exists await StorageManager.instance.secure.remove(StorageKeys.userSession); - _logger.d('Account cleared successfully'); } catch (e) { _logger.e('Failed to clear account', error: e); } @@ -241,14 +224,10 @@ class AuthRepositoryImpl implements AuthRepository { @override Future initiateOAuth(String handle) async { try { - _logger.i('Initiating OAuth for handle: $handle'); - // Resolve handle to DID final at = ATProto.anonymous(service: 'public.api.bsky.app'); - _logger.d('Resolving handle: $handle'); final didRes = await at.identity.resolveHandle(handle: handle); final resolvedDid = didRes.data.did; - _logger.d('Resolved DID: $resolvedDid'); final didDoc = await _fetchDidDocument(resolvedDid); final pdsEndpoint = _extractPdsEndpoint(didDoc); @@ -258,8 +237,6 @@ class AuthRepositoryImpl implements AuthRepository { throw Exception('PDS endpoint not found in DID document'); } - _logger.d('Found PDS endpoint: $pdsEndpoint'); - // Store user info for later _did = resolvedDid; _handle = handle; @@ -269,7 +246,6 @@ class AuthRepositoryImpl implements AuthRepository { final metadata = await _getCachedClientMetadata(); // Resolve OAuth server from PDS endpoint _oauthServer = await resolveOAuthServer(pdsEndpoint); - _logger.d('Resolved OAuth server: $_oauthServer'); _oauthClient = OAuthClient(metadata, service: _oauthServer!); // Start OAuth authorization @@ -292,7 +268,6 @@ class AuthRepositoryImpl implements AuthRepository { }), ); - _logger.i('OAuth authorization URL generated'); return authUrl.toString(); } catch (e) { _logger.e('Failed to initiate OAuth', error: e); @@ -303,15 +278,12 @@ class AuthRepositoryImpl implements AuthRepository { @override Future initiateOAuthWithService(String service) async { try { - _logger.i('Initiating OAuth with service: $service'); - // Store service for later _pdsEndpoint = 'https://$service'; _oauthServer = service; // Get client metadata (cached) final metadata = await _getCachedClientMetadata(); - _logger.d('Using OAuth server: $service'); _oauthClient = OAuthClient(metadata, service: _oauthServer!); // Start OAuth authorization without login hint @@ -332,7 +304,6 @@ class AuthRepositoryImpl implements AuthRepository { }), ); - _logger.i('OAuth authorization URL generated'); return authUrl.toString(); } catch (e) { _logger.e('Failed to initiate OAuth with service', error: e); @@ -427,7 +398,6 @@ class AuthRepositoryImpl implements AuthRepository { final sessionResponse = await _atProto!.server.getSession(); _did = sessionResponse.data.did; _handle = sessionResponse.data.handle; - _logger.d('Fetched session info - DID: $_did, Handle: $_handle'); } catch (e) { _logger.e('Failed to fetch session info', error: e); return LoginResult.failed('Failed to get session info: $e'); @@ -443,7 +413,6 @@ class AuthRepositoryImpl implements AuthRepository { ); _pendingContext = null; - _logger.i('OAuth login successful for user: $_handle'); return LoginResult.success(); } catch (e, stackTrace) { _logger.e('OAuth callback failed', error: e, stackTrace: stackTrace); @@ -454,7 +423,6 @@ class AuthRepositoryImpl implements AuthRepository { @override Future logout() async { try { - _logger.i('Logging out user: $_handle'); await _clearSavedSession(); _oauthSession = null; _atProto = null; @@ -464,7 +432,6 @@ class AuthRepositoryImpl implements AuthRepository { _oauthServer = null; _oauthClient = null; _pendingContext = null; - _logger.i('Logout successful'); } catch (e) { _logger.e('Logout failed', error: e); } @@ -476,29 +443,19 @@ class AuthRepositoryImpl implements AuthRepository { await initializationComplete; if (_atProto == null || _oauthSession == null) { - _logger.d('No session to validate'); return false; } try { - _logger.d('Validating OAuth session for user: $_handle'); await _atProto!.identity.resolveHandle(handle: _handle ?? ''); - _logger.d('Session validation successful'); return true; } catch (e) { - _logger.w( - 'Session validation failed, attempting token refresh', - error: e, - ); - // Try to refresh the token before giving up final refreshed = await refreshToken(); if (refreshed) { - _logger.i('Token refresh successful, session is now valid'); return true; } - _logger.w('Token refresh failed, logging out'); await logout(); return false; } @@ -508,20 +465,15 @@ class AuthRepositoryImpl implements AuthRepository { Future refreshToken() async { try { if (_oauthSession == null || _oauthClient == null) { - _logger.w('No OAuth session or client to refresh'); - // Try to recreate OAuth client if we have a session but no client if (_oauthSession != null && _oauthServer != null) { final metadata = await _getCachedClientMetadata(); _oauthClient = OAuthClient(metadata, service: _oauthServer!); - _logger.d('OAuthClient recreated with service: $_oauthServer'); } else { - _logger.w('Cannot refresh: missing session or OAuth server'); return false; } } - _logger.i('Refreshing OAuth token'); final refreshedSession = await _oauthClient!.refresh(_oauthSession!); _oauthSession = refreshedSession; @@ -534,7 +486,6 @@ class AuthRepositoryImpl implements AuthRepository { ); await _saveSession(); - _logger.i('OAuth token refresh successful'); return true; } catch (e) { _logger.e('OAuth token refresh failed', error: e); diff --git a/lib/src/core/design_system/templates/recording_page_template.dart b/lib/src/core/design_system/templates/recording_page_template.dart index 3162bfe2..724c9958 100644 --- a/lib/src/core/design_system/templates/recording_page_template.dart +++ b/lib/src/core/design_system/templates/recording_page_template.dart @@ -47,7 +47,7 @@ class RecordingPageTemplate extends StatelessWidget { @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); - final footerHeight = kBottomNavigationBarHeight + 12; + const footerHeight = kBottomNavigationBarHeight + 12; const borderRadius = BorderRadius.all(Radius.circular(20)); // Calculate scale based on camera aspect ratio and screen aspect ratio @@ -100,9 +100,9 @@ class RecordingPageTemplate extends StatelessWidget { ), ), ), - SizedBox( + const SizedBox( height: footerHeight, - child: const ColoredBox(color: Colors.black), + child: ColoredBox(color: Colors.black), ), ], ), diff --git a/lib/src/core/l10n/app_localizations_en.dart b/lib/src/core/l10n/app_localizations_en.dart index 7aff836c..df10fcbc 100644 --- a/lib/src/core/l10n/app_localizations_en.dart +++ b/lib/src/core/l10n/app_localizations_en.dart @@ -1,5 +1,3 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; import 'app_localizations.dart'; // ignore_for_file: type=lint diff --git a/lib/src/core/media/create_media_actions.dart b/lib/src/core/media/create_media_actions.dart index 5258a733..ad3819a5 100644 --- a/lib/src/core/media/create_media_actions.dart +++ b/lib/src/core/media/create_media_actions.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:image_picker/image_picker.dart'; import 'package:pro_video_editor/pro_video_editor.dart'; +import 'package:spark/src/core/pro_video_editor/models/video_editor_result.dart'; import 'package:spark/src/core/pro_video_editor/pro_video_editor_repository.dart'; import 'package:spark/src/core/routing/app_router.dart'; import 'package:spark/src/features/posting/ui/pages/recording_page.dart'; @@ -48,28 +49,35 @@ class CreateMediaActions { source: ImageSource.gallery, maxDuration: const Duration(seconds: 180), ); - if (pickedVideo != null && context.mounted) { - final editorVideo = EditorVideo.file(File(pickedVideo.path)); - final repository = GetIt.I(); - final result = storyMode - ? await repository.openStoryVideoEditor(context, editorVideo) - : await repository.openVideoEditor(context, editorVideo); - if (result != null && context.mounted) { - if (storyMode) { - // For stories, post directly - await context.router.push( - StoryPostRoute(videoPath: result.video.path), - ); - } else { - // For posts, go to review - await context.router.push( - VideoReviewRoute( - videoPath: result.video.path, - storyMode: storyMode, - soundRef: result.soundRef, - ), - ); - } + if (pickedVideo == null) return; + if (!context.mounted) return; + + final editorVideo = EditorVideo.file(File(pickedVideo.path)); + final repository = GetIt.I(); + VideoEditorResult? result; + if (storyMode) { + if (!context.mounted) return; + result = await repository.openStoryVideoEditor(context, editorVideo); + } else { + if (!context.mounted) return; + result = await repository.openVideoEditor(context, editorVideo); + } + + if (result != null && context.mounted) { + if (storyMode) { + // For stories, post directly + await context.router.push( + StoryPostRoute(videoPath: result.video.path), + ); + } else { + // For posts, go to review + await context.router.push( + VideoReviewRoute( + videoPath: result.video.path, + storyMode: storyMode, + soundRef: result.soundRef, + ), + ); } } }; diff --git a/lib/src/core/network/atproto/data/repositories/feed_repository_impl.dart b/lib/src/core/network/atproto/data/repositories/feed_repository_impl.dart index b37701ed..2289403b 100644 --- a/lib/src/core/network/atproto/data/repositories/feed_repository_impl.dart +++ b/lib/src/core/network/atproto/data/repositories/feed_repository_impl.dart @@ -79,7 +79,7 @@ class FeedRepositoryImpl implements FeedRepository { if (hasMedia(parsedPost)) { posts.add(parsedPost); - } else {} + } } catch (e) { _logger.w('Failed to parse $source post, skipping: $e'); } diff --git a/lib/src/core/network/messages/data/repository/messages_repository_xrpc.dart b/lib/src/core/network/messages/data/repository/messages_repository_xrpc.dart index 8b3895c1..4811bbd5 100644 --- a/lib/src/core/network/messages/data/repository/messages_repository_xrpc.dart +++ b/lib/src/core/network/messages/data/repository/messages_repository_xrpc.dart @@ -31,8 +31,6 @@ class MessagesRepositoryXrpc implements MessagesRepository { queryParameters: params.isEmpty ? null : params, ); - _logger.d('GET $url'); - final response = await http.get( url, headers: { @@ -41,8 +39,6 @@ class MessagesRepositoryXrpc implements MessagesRepository { }, ); - _logger.d('Response: ${response.statusCode}'); - if (response.statusCode == 200) { return jsonDecode(response.body) as Map; } else { @@ -65,10 +61,6 @@ class MessagesRepositoryXrpc implements MessagesRepository { final token = await _serviceAuthHelper.getServiceToken(nsid); final url = Uri.parse('$_baseUrl/xrpc/$nsid'); - _logger - ..d('POST $url') - ..d('Body: ${jsonEncode(body)}'); - final response = await http.post( url, headers: { @@ -79,8 +71,6 @@ class MessagesRepositoryXrpc implements MessagesRepository { body: jsonEncode(body), ); - _logger.d('Response: ${response.statusCode}'); - if (response.statusCode == 204) { // No content, but considered success return {}; @@ -149,8 +139,6 @@ class MessagesRepositoryXrpc implements MessagesRepository { 'so.sprk.chat.convo.getConvoForMembers', ); - _logger.d('GET $url'); - final response = await http.get( url, headers: { @@ -159,8 +147,6 @@ class MessagesRepositoryXrpc implements MessagesRepository { }, ); - _logger.d('Response: ${response.statusCode}'); - if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; return ConvoView.fromJson(data['convo'] as Map); diff --git a/lib/src/core/network/xrpc/service_auth_helper.dart b/lib/src/core/network/xrpc/service_auth_helper.dart index a06b51a8..56748e91 100644 --- a/lib/src/core/network/xrpc/service_auth_helper.dart +++ b/lib/src/core/network/xrpc/service_auth_helper.dart @@ -29,7 +29,6 @@ class ServiceAuthHelper { DateTime.now().isBefore( cached.expiry.subtract(const Duration(seconds: 30)), )) { - _logger.d('Using cached service token for $nsid'); return cached.token; } @@ -39,10 +38,6 @@ class ServiceAuthHelper { throw Exception('Not authenticated - ATProto client not available'); } - _logger.d( - 'Requesting service auth token for $nsid with aud: $serviceDid', - ); - // Calculate expiration (60 seconds from now) final exp = DateTime.now() @@ -65,7 +60,6 @@ class ServiceAuthHelper { expiry: DateTime.fromMillisecondsSinceEpoch(exp * 1000), ); - _logger.d('Successfully obtained service auth token for $nsid'); return token; } catch (e) { _logger.e('Failed to get service auth token for $nsid', error: e); @@ -76,12 +70,10 @@ class ServiceAuthHelper { /// Clears the token cache void clearCache() { _tokenCache.clear(); - _logger.d('Service auth token cache cleared'); } /// Clears a specific token from cache void clearToken(String nsid) { _tokenCache.remove(nsid); - _logger.d('Cleared service auth token for $nsid'); } } diff --git a/lib/src/core/notifications/push_notification_service.dart b/lib/src/core/notifications/push_notification_service.dart index 84fdb999..c151b6ab 100644 --- a/lib/src/core/notifications/push_notification_service.dart +++ b/lib/src/core/notifications/push_notification_service.dart @@ -11,9 +11,7 @@ import 'package:spark/src/core/utils/logging/logger.dart'; /// Service for managing push notifications via Firebase Cloud Messaging class PushNotificationService { - PushNotificationService() { - _logger.v('PushNotificationService initialized'); - } + PushNotificationService(); late final FirebaseMessaging _messaging; final SparkLogger _logger = GetIt.instance().getLogger( @@ -31,24 +29,19 @@ class PushNotificationService { /// Initializes Firebase without requesting permissions /// Permissions should be requested via [requestPermissionAndGetToken] Future initialize() async { - _logger.i('Initializing push notification service'); - try { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); _messaging = FirebaseMessaging.instance; - _logger.d('Firebase initialized'); // Listen for token refresh _messaging.onTokenRefresh.listen(_onTokenRefresh); // Check if badge is supported on this device _badgeSupported = await AppBadgePlus.isSupported(); - _logger.d('Badge support: $_badgeSupported'); _initialized = true; - _logger.i('Push notification service initialized successfully'); // Set up message handlers for deep linking await _setupMessageHandlers(); @@ -69,21 +62,16 @@ class PushNotificationService { // Handle notification tap when app was terminated final initialMessage = await _messaging.getInitialMessage(); if (initialMessage != null) { - _logger.i('App opened from terminated state via notification'); // Queue the navigation - will be processed after auth completes _pendingNotification = initialMessage; } // Handle foreground messages (for badge updates) FirebaseMessaging.onMessage.listen(_handleForegroundMessage); - - _logger.d('FCM message handlers set up'); } /// Handles notification tap when app is in background or foreground void _handleNotificationTap(RemoteMessage message) { - _logger.i('Notification tapped: ${message.data}'); - final data = message.data; final reason = data['reason'] as String?; final author = data['author'] as String?; @@ -91,7 +79,6 @@ class PushNotificationService { final reasonSubject = data['reasonSubject'] as String?; if (!GetIt.instance.isRegistered()) { - _logger.w('AppRouter not registered, queueing navigation'); _pendingNotification = message; return; } @@ -100,29 +87,21 @@ class PushNotificationService { if (reason == 'follow' && author != null) { // Navigate to profile for follow notifications - _logger.d('Navigating to profile: $author'); router.push(ProfileRoute(did: author)); } else if (reasonSubject != null) { // For likes/reposts, navigate to the subject (the post being liked/reposted) - _logger.d('Navigating to post (reasonSubject): $reasonSubject'); router.push(StandalonePostRoute(postUri: reasonSubject)); } else if (recordUri != null) { // For replies/mentions, navigate to the record itself - _logger.d('Navigating to post (recordUri): $recordUri'); router.push(StandalonePostRoute(postUri: recordUri)); } else if (author != null) { // Fallback to author profile - _logger.d('Navigating to author profile (fallback): $author'); router.push(ProfileRoute(did: author)); - } else { - _logger.w('No valid navigation target in notification data'); } } /// Handles foreground messages (updates badge count) void _handleForegroundMessage(RemoteMessage message) { - _logger.d('Foreground message received: ${message.notification?.title}'); - // Badge is already set by the server in the APNS payload // We could optionally show an in-app notification here } @@ -133,7 +112,6 @@ class PushNotificationService { /// Processes pending notification navigation (call after auth completes) void processPendingNotification() { if (_pendingNotification != null) { - _logger.i('Processing pending notification navigation'); _handleNotificationTap(_pendingNotification!); _pendingNotification = null; } @@ -158,22 +136,11 @@ class PushNotificationService { Future requestPermission() async { if (!_initialized) return false; - _logger.d('Requesting notification permissions'); - try { final settings = await _messaging.requestPermission(); - if (settings.authorizationStatus == AuthorizationStatus.authorized) { - _logger.i('User granted notification permission'); - return true; - } else if (settings.authorizationStatus == - AuthorizationStatus.provisional) { - _logger.i('User granted provisional notification permission'); - return true; - } else { - _logger.w('User denied notification permission'); - return false; - } + return settings.authorizationStatus == AuthorizationStatus.authorized || + settings.authorizationStatus == AuthorizationStatus.provisional; } catch (e, stackTrace) { _logger.e( 'Failed to request permission', @@ -201,7 +168,6 @@ class PushNotificationService { /// Handles FCM token refresh void _onTokenRefresh(String token) { - _logger.d('FCM token refreshed: ${token.substring(0, 10)}...'); _currentToken = token; // Token refresh registration is handled by the auth flow // which will call registerPush with the new token @@ -213,11 +179,7 @@ class PushNotificationService { if (!_initialized) return null; try { - _currentToken ??= await _messaging.getToken(); - if (_currentToken != null) { - _logger.d('FCM token obtained: ${_currentToken!.substring(0, 10)}...'); - } - return _currentToken; + return _currentToken ??= await _messaging.getToken(); } catch (e) { _logger.e('Failed to get FCM token', error: e); return null; @@ -236,7 +198,6 @@ class PushNotificationService { try { await AppBadgePlus.updateBadge(0); - _logger.d('Badge cleared'); } catch (e, stackTrace) { _logger.e('Failed to clear badge', error: e, stackTrace: stackTrace); } @@ -248,7 +209,6 @@ class PushNotificationService { try { await AppBadgePlus.updateBadge(count); - _logger.d('Badge updated to $count'); } catch (e, stackTrace) { _logger.e('Failed to update badge', error: e, stackTrace: stackTrace); } diff --git a/lib/src/core/pro_image_editor/story_image_editor_configs.dart b/lib/src/core/pro_image_editor/story_image_editor_configs.dart index 382a7a77..29380ecd 100644 --- a/lib/src/core/pro_image_editor/story_image_editor_configs.dart +++ b/lib/src/core/pro_image_editor/story_image_editor_configs.dart @@ -84,7 +84,6 @@ class StoryImageEditorConfigs { stream: rebuildStream, ), wrapBody: (editor, rebuildStream, content) { - // Fill behind content so no letterboxing shows as dark lines on sides return ClipRRect( borderRadius: _storyEditorBorderRadius, child: Container( diff --git a/lib/src/core/pro_image_editor/ui/story_image_editor_page.dart b/lib/src/core/pro_image_editor/ui/story_image_editor_page.dart index 8c87573d..6b39f0aa 100644 --- a/lib/src/core/pro_image_editor/ui/story_image_editor_page.dart +++ b/lib/src/core/pro_image_editor/ui/story_image_editor_page.dart @@ -181,15 +181,13 @@ class _StoryImageEditorPageState extends State { return Scaffold( backgroundColor: Colors.black, body: ProImageEditor.file( - _croppedImageFile!, + _croppedImageFile, key: _editorKey, callbacks: ProImageEditorCallbacks( onImageEditingComplete: _onImageEditingComplete, onCloseEditor: _onCloseEditor, stickerEditorCallbacks: StickerEditorCallbacks( - onSearchChanged: (value) { - debugPrint('Sticker search: $value'); - }, + onSearchChanged: (_) {}, ), ), configs: _configs, @@ -270,9 +268,7 @@ class _StoryBlankCanvasEditorPageState }); _addBackgroundImageLayer(); } - } catch (e) { - debugPrint('Failed to load image size: $e'); - } + } catch (_) {} } void _initializeEditor() { @@ -372,9 +368,7 @@ class _StoryBlankCanvasEditorPageState onAfterViewInit: _addBackgroundImageLayer, ), stickerEditorCallbacks: StickerEditorCallbacks( - onSearchChanged: (value) { - debugPrint('Sticker search: $value'); - }, + onSearchChanged: (_) {}, ), ), configs: _configs.copyWith( diff --git a/lib/src/core/pro_image_editor/utils/story_image_cropper.dart b/lib/src/core/pro_image_editor/utils/story_image_cropper.dart index 9f429285..f0ebd21a 100644 --- a/lib/src/core/pro_image_editor/utils/story_image_cropper.dart +++ b/lib/src/core/pro_image_editor/utils/story_image_cropper.dart @@ -53,7 +53,7 @@ class StoryImageCropper { // Draw the cropped portion scaled to target size final srcRect = Rect.fromLTWH(cropX, cropY, cropWidth, cropHeight); - final dstRect = Rect.fromLTWH(0, 0, targetWidth, targetHeight); + const dstRect = Rect.fromLTWH(0, 0, targetWidth, targetHeight); canvas.drawImageRect(image, srcRect, dstRect, Paint()); diff --git a/lib/src/core/pro_video_editor/pro_video_editor_repository_impl.dart b/lib/src/core/pro_video_editor/pro_video_editor_repository_impl.dart index c4b10c10..02e33991 100644 --- a/lib/src/core/pro_video_editor/pro_video_editor_repository_impl.dart +++ b/lib/src/core/pro_video_editor/pro_video_editor_repository_impl.dart @@ -103,8 +103,9 @@ class ProVideoEditorRepositoryImpl implements ProVideoEditorRepository { }) { return StoryBlankCanvasEditorPage.open( context, - backgroundImage: - backgroundImage != null ? File(backgroundImage.path) : null, + backgroundImage: backgroundImage != null + ? File(backgroundImage.path) + : null, backgroundColor: backgroundColor, ); } diff --git a/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart b/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart index adb34f72..21619d57 100644 --- a/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart +++ b/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart @@ -38,8 +38,7 @@ class AudioWaveformExtractor { ); return _normalizeWaveform(waveformData); - } catch (e) { - debugPrint('Failed to extract waveform: $e'); + } catch (_) { return []; } } diff --git a/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart b/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart index 38de120c..2d5f28d2 100644 --- a/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart +++ b/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart @@ -296,9 +296,7 @@ class _VideoEditorGroundedPageState extends State { ), ), ); - } catch (e) { - debugPrint('Failed to fetch trending audios: $e'); - } + } catch (_) {} return audioTracks; } @@ -639,10 +637,7 @@ class _VideoEditorGroundedPageState extends State { ), mainEditorCallbacks: const MainEditorCallbacks(), stickerEditorCallbacks: StickerEditorCallbacks( - onSearchChanged: (value) { - /// Filter your stickers - debugPrint(value); - }, + onSearchChanged: (_) {}, ), ), configs: _configs, diff --git a/lib/src/core/pro_video_editor/ui/widgets/player/video_player_widget.dart b/lib/src/core/pro_video_editor/ui/widgets/player/video_player_widget.dart index 80f01f45..ccb20920 100644 --- a/lib/src/core/pro_video_editor/ui/widgets/player/video_player_widget.dart +++ b/lib/src/core/pro_video_editor/ui/widgets/player/video_player_widget.dart @@ -56,7 +56,9 @@ Future createVideoPlayerControllerFromEditorVideo( if (path != null && path.isNotEmpty) { return VideoPlayerController.networkUrl(Uri.file(path)); } - } catch (_) {} + } catch (_) { + // File doesn't expose path, will fall back to network URL + } } // Fallback controller (should not happen when a valid video is provided) return VideoPlayerController.networkUrl(Uri.parse('about:blank')); diff --git a/lib/src/core/routing/app_router.dart b/lib/src/core/routing/app_router.dart index 0247e610..82d66343 100644 --- a/lib/src/core/routing/app_router.dart +++ b/lib/src/core/routing/app_router.dart @@ -9,17 +9,11 @@ import 'package:spark/src/core/auth/data/repositories/auth_repository.dart'; import 'package:spark/src/core/auth/data/repositories/onboarding_repository.dart'; import 'package:spark/src/core/network/atproto/atproto.dart'; import 'package:spark/src/core/routing/pages.dart'; -import 'package:spark/src/core/utils/logging/log_service.dart'; -import 'package:spark/src/core/utils/logging/logger.dart'; import 'package:spark/src/features/profile/ui/pages/user_list_page.dart'; part 'app_router.gr.dart'; class AuthGuard extends AutoRouteGuard { - final SparkLogger _logger = GetIt.instance().getLogger( - 'AuthGuard', - ); - @override Future onNavigation( NavigationResolver resolver, @@ -32,7 +26,6 @@ class AuthGuard extends AutoRouteGuard { final hasSpark = await onboardingRepository.hasSparkProfile(); if (!hasSpark) { - _logger.d('No Spark profile found, redirecting to register'); resolver.redirectUntil(const RegisterRoute()); return; } @@ -40,15 +33,12 @@ class AuthGuard extends AutoRouteGuard { final isSessionValid = await authRepository.validateSession(); if (!isSessionValid) { - _logger.d('Session invalid, redirecting to login'); resolver.redirectUntil(const LoginRoute()); return; } - _logger.d('Authentication valid, continuing to route'); resolver.next(); } catch (e) { - _logger.e('Error during auth check', error: e); resolver.redirectUntil(const RegisterRoute()); } } diff --git a/lib/src/core/storage/cache/download_manager_impl.dart b/lib/src/core/storage/cache/download_manager_impl.dart index b59b8ca7..d4f1956f 100644 --- a/lib/src/core/storage/cache/download_manager_impl.dart +++ b/lib/src/core/storage/cache/download_manager_impl.dart @@ -23,7 +23,6 @@ class DownloadManagerImpl implements DownloadManagerInterface { type: 'timeline', config: SavedFeed(type: 'timeline', value: 'following', pinned: true), ); - _logger.d('DownloadManager initialized with default feed'); } @override @@ -44,7 +43,6 @@ class DownloadManagerImpl implements DownloadManagerInterface { @override void setActiveFeed(Feed feed) { - _logger.d('Setting active feed to: ${feed.config.id}'); _activeFeed = feed; _updateTaskPriorities(); _processQueue(); // Re-evaluate queue processing if active feed changed @@ -74,7 +72,6 @@ class DownloadManagerImpl implements DownloadManagerInterface { void submitTask(DownloadTask task) { // Prevent duplicate tasks for the same post if one is already pending/active if (_tasks.contains(task)) { - _logger.d('Task for ${task.uri} already in queue. Skipping.'); return; } @@ -88,7 +85,6 @@ class DownloadManagerImpl implements DownloadManagerInterface { Future _processQueue() async { if (_isProcessing) return; // Already processing _isProcessing = true; - _logger.d('Processing queue. Queue size: ${_tasks.length}'); final newTasks = []; while (_tasks.isNotEmpty) { @@ -124,7 +120,6 @@ class DownloadManagerImpl implements DownloadManagerInterface { } _tasks.remove(task); // Ensure removal on unhandled pool error }); - _logger.d('Task ${task.uri} submitted to pool for execution.'); } if (task.status != DownloadTaskStatus.completed && task.status != DownloadTaskStatus.failed) { @@ -135,16 +130,13 @@ class DownloadManagerImpl implements DownloadManagerInterface { _tasks.addAll(newTasks); _isProcessing = false; - _logger.d('Finished a processing pass. Queue size: ${_tasks.length}'); } @override Future dispose() async { - _logger.d('Disposing DownloadManager...'); _cancelAllPendingTasks(); // Attempt to clean up await _pool .close(); // Closes the pool and waits for active tasks to complete - _logger.d('DownloadManager disposed.'); } bool _areTherePendingActiveFeedTasks() { @@ -156,17 +148,9 @@ class DownloadManagerImpl implements DownloadManagerInterface { } Future _executeTask(DownloadTask task) async { - _logger.d( - 'Executing task: ${task.uri} for feed ${task.feed.config.id} ' - 'with priority ${task.priority}', - ); if (_activeFeed != task.feed && task.priority > activeFeedPriority && _areTherePendingActiveFeedTasks()) { - _logger.d( - 'Task ${task.uri} is for an inactive feed, but there are still ' - 'pending active feed tasks. Skipping.', - ); return; } try { @@ -174,8 +158,6 @@ class DownloadManagerImpl implements DownloadManagerInterface { // This is a softer check than full cancellation. task.status = DownloadTaskStatus.active; - - _logger.d('Caching media for post: ${task.uri}'); // Actual caching work - start downloading the media switch (task.post.media) { case MediaViewVideo(): @@ -191,7 +173,6 @@ class DownloadManagerImpl implements DownloadManagerInterface { ), ), ); - _logger.d('Video file successfully cached: ${task.post.videoUrl}'); case MediaViewImages(): for (final url in task.post.imageUrls) { // Download the image and verify it's cached @@ -222,20 +203,11 @@ class DownloadManagerImpl implements DownloadManagerInterface { ), ), ); - _logger.d( - 'Video file successfully cached: ${task.post.videoUrl}', - ); case MediaViewImage() || MediaViewImages() || MediaViewBskyImages(): for (final url in task.post.imageUrls) { // Download the image and verify it's cached - final fileInfo = await CachedNetworkImageProvider - .defaultCacheManager + await CachedNetworkImageProvider.defaultCacheManager .downloadFile(url, key: url); - if (fileInfo.statusCode != 200) { - _logger.w( - 'Image file was not properly cached after download: $url', - ); - } } default: throw Exception('Unsupported media type: ${media.runtimeType}'); @@ -263,11 +235,10 @@ class DownloadManagerImpl implements DownloadManagerInterface { } task.status = DownloadTaskStatus.completed; - _logger.d('Task ${task.uri} completed successfully.'); task.onComplete(task); } catch (e, s) { task.status = DownloadTaskStatus.failed; - _logger.e('Task ${task.uri} failed: $e', error: e, stackTrace: s); + _logger.w('Failed to cache media for ${task.uri}'); task.onError(task, e, s); } finally { // Remove from the main queue regardless of outcome, as it's processed. diff --git a/lib/src/core/utils/logging/logger_factory.dart b/lib/src/core/utils/logging/logger_factory.dart index c2cbf12c..eca6a3cd 100644 --- a/lib/src/core/utils/logging/logger_factory.dart +++ b/lib/src/core/utils/logging/logger_factory.dart @@ -9,7 +9,7 @@ import 'package:spark/src/core/utils/logging/logger.dart'; /// Factory for creating logger instances class LoggerFactory { /// Global minimum log level - static LogLevel _globalMinLevel = kDebugMode ? LogLevel.debug : LogLevel.info; + static LogLevel _globalMinLevel = LogLevel.warning; /// List of default outputs static final List _defaultOutputs = [ @@ -72,6 +72,6 @@ class LoggerFactory { if (!kIsWeb) { _defaultOutputs.add(FileOutput()); } - _globalMinLevel = kDebugMode ? LogLevel.debug : LogLevel.info; + _globalMinLevel = LogLevel.warning; } } diff --git a/lib/src/core/utils/logging/riverpod_logger.dart b/lib/src/core/utils/logging/riverpod_logger.dart index 241bcd4c..114e36c2 100644 --- a/lib/src/core/utils/logging/riverpod_logger.dart +++ b/lib/src/core/utils/logging/riverpod_logger.dart @@ -1,31 +1,21 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:get_it/get_it.dart'; - -import 'package:spark/src/core/utils/logging/logging.dart'; /// A ProviderObserver that logs provider changes +/// +/// Logging is currently disabled to reduce console noise. +/// Re-enable by uncommenting the logging logic in didUpdateProvider. final class SparkRiverpodLogger extends ProviderObserver { /// Constructor - SparkRiverpodLogger({LogService? logService}) - : _logService = logService ?? GetIt.instance() { - _logger = _logService.getLogger('Riverpod'); - } - final LogService _logService; - late final SparkLogger _logger; + SparkRiverpodLogger(); @override void didAddProvider(ProviderObserverContext context, Object? value) { - _logger.d( - '${context.provider.name ?? context.provider.runtimeType} ' - 'added: ${_truncateIfNeeded(value.toString())}', - ); + // Provider lifecycle logging disabled to reduce noise } @override void didDisposeProvider(ProviderObserverContext context) { - _logger.d( - '${context.provider.name ?? context.provider.runtimeType} was disposed.', - ); + // Provider lifecycle logging disabled to reduce noise } @override @@ -34,320 +24,6 @@ final class SparkRiverpodLogger extends ProviderObserver { Object? previousValue, Object? newValue, ) { - // Skip logging if previous and new values are identical - if (previousValue == newValue) return; - - // Also skip if they have the same string representation - if (previousValue.toString() == newValue.toString()) return; - - final diff = _generateDiff(previousValue, newValue); - if (diff.isNotEmpty && diff != 'No changes') { - _logger.d( - '${context.provider.name ?? context.provider.runtimeType} ' - 'changed: $diff', - ); - } - } - - /// Generates a diff showing what changed between two values - String _generateDiff(Object? previous, Object? current) { - // Handle null cases - if (previous == null && current == null) return 'No changes'; - if (previous == null) { - return 'Added: ${_truncateIfNeeded(current.toString())}'; - } - if (current == null) { - return 'Removed: ${_truncateIfNeeded(previous.toString())}'; - } - - // Handle Map types - if (previous is Map && current is Map) { - return _generateMapDiff(previous, current); - } - - // Handle List types - if (previous is List && current is List) { - return _generateListDiff(previous, current); - } - - // Handle custom objects with structured toString() - final previousStr = previous.toString(); - final currentStr = current.toString(); - - if (previousStr == currentStr) return 'No visible changes'; - - final structuredDiff = _tryParseStructuredObjectDiff( - previousStr, - currentStr, - ); - if (structuredDiff != null && structuredDiff.isNotEmpty) { - return structuredDiff; - } - - // For very long objects, try to show just class name and indicate change - if (previousStr.length > 1000 || currentStr.length > 1000) { - final className = _extractClassName(previousStr); - return className != null - ? '$className fields changed' - : 'Object changed (too large to diff)'; - } - - // Fallback to simple before → after for smaller objects - return '${_truncateIfNeeded(previousStr)} → ' - '${_truncateIfNeeded(currentStr)}'; - } - - /// Generates diff for Map objects - String _generateMapDiff(Map previous, Map current) { - final changes = []; - final allKeys = {...previous.keys, ...current.keys}; - - for (final key in allKeys) { - final prevValue = previous[key]; - final currValue = current[key]; - - if (!previous.containsKey(key)) { - changes.add('+ $key: ${_truncateIfNeeded(currValue.toString())}'); - } else if (!current.containsKey(key)) { - changes.add('- $key: ${_truncateIfNeeded(prevValue.toString())}'); - } else if (prevValue != currValue) { - changes.add( - '~ $key: ${_truncateIfNeeded(prevValue.toString())} → ' - '${_truncateIfNeeded(currValue.toString())}', - ); - } - } - - return changes.isEmpty ? 'No changes' : changes.join(', '); - } - - /// Generates diff for List objects - String _generateListDiff(List previous, List current) { - final changes = []; - - // Find added items (items in current but not in previous) - final added = []; - for (final item in current) { - if (!previous.contains(item)) { - added.add(item); - } - } - - // Find removed items (items in previous but not in current) - final removed = []; - for (final item in previous) { - if (!current.contains(item)) { - removed.add(item); - } - } - - // Add length change if different - if (previous.length != current.length) { - changes.add('length: ${previous.length} → ${current.length}'); - } - - // Add removed items - if (removed.isNotEmpty) { - if (removed.length <= 3) { - changes.add( - 'removed: [${removed.map((e) => _truncateIfNeeded( - e.toString(), - maxLength: 50, - )).join(', ')}]', - ); - } else { - changes.add('removed: ${removed.length} items'); - } - } - - // Add added items - if (added.isNotEmpty) { - if (added.length <= 3) { - changes.add( - 'added: [${added.map((e) => _truncateIfNeeded( - e.toString(), - maxLength: 50, - )).join(', ')}]', - ); - } else { - changes.add('added: ${added.length} items'); - } - } - - // If no adds/removes but lists are different, check for positional changes - if (changes.isEmpty && previous.length == current.length) { - for (var i = 0; i < previous.length; i++) { - if (previous[i] != current[i]) { - changes.add( - '[$i]: ${_truncateIfNeeded(previous[i].toString(), maxLength: 50)} ' - '→ ${_truncateIfNeeded(current[i].toString(), maxLength: 50)}', - ); - if (changes.length >= 3) { - changes.add('... and ${previous.length - i - 1} more changes'); - break; - } - } - } - } - - return changes.isEmpty ? 'No changes' : changes.join(', '); - } - - String? _tryParseStructuredObjectDiff(String previous, String current) { - final prevFields = _parseStructuredObject(previous); - final currFields = _parseStructuredObject(current); - - if (prevFields == null || currFields == null) return null; - - final changes = []; - final allKeys = {...prevFields.keys, ...currFields.keys}; - - for (final key in allKeys) { - final prevValue = prevFields[key]; - final currValue = currFields[key]; - - if (!prevFields.containsKey(key)) { - changes.add('+ $key: ${_truncateIfNeeded(currValue!, maxLength: 200)}'); - } else if (!currFields.containsKey(key)) { - changes.add('- $key: ${_truncateIfNeeded(prevValue!, maxLength: 200)}'); - } else if (prevValue != currValue) { - // Skip logging very large nested objects - just show field name changed - if (prevValue!.length > 500 || currValue!.length > 500) { - changes.add('$key: '); - } else { - // Check if the field values are lists and handle them specially - final listDiff = _tryParseFieldAsListDiff(prevValue, currValue); - if (listDiff != null) { - changes.add('$key: $listDiff'); - } else { - changes.add( - '$key: ${_truncateIfNeeded(prevValue, maxLength: 100)} → ' - '${_truncateIfNeeded(currValue, maxLength: 100)}', - ); - } - } - } - } - - final result = changes.join(', '); - return result.isEmpty ? null : result; - } - - /// Extracts the class name from a structured object string - String? _extractClassName(String objStr) { - final match = RegExp(r'^([^(]+)\(').firstMatch(objStr.trim()); - return match?.group(1)?.trim(); - } - - Map? _parseStructuredObject(String objStr) { - // Match pattern like ClassName(...) - final match = RegExp(r'^[^(]+\((.+)\)$').firstMatch(objStr.trim()); - if (match == null) return null; - - final content = match.group(1)!; - final fields = {}; - - // Split by comma, but be careful about nested structures - final parts = _splitFields(content); - - for (final part in parts) { - final colonIndex = part.indexOf(':'); - if (colonIndex == -1) continue; - - final key = part.substring(0, colonIndex).trim(); - final value = part.substring(colonIndex + 1).trim(); - fields[key] = value; - } - - return fields.isEmpty ? null : fields; - } - - /// Splits field strings by comma, handling nested structures - List _splitFields(String content) { - final parts = []; - final buffer = StringBuffer(); - var depth = 0; - var inString = false; - String? currentQuote; - - for (var i = 0; i < content.length; i++) { - final char = content[i]; - - // Handle string literals - if (!inString && (char == '"' || char == "'")) { - inString = true; - currentQuote = char; - } else if (inString && char == currentQuote) { - // Check if it's not escaped - if (i == 0 || content[i - 1] != r'\') { - inString = false; - currentQuote = null; - } - } else if (!inString) { - // Handle nesting - if (char == '(' || char == '[' || char == '{') { - depth++; - } else if (char == ')' || char == ']' || char == '}') { - depth--; - } else if (char == ',' && depth == 0) { - // Found a top-level comma separator - final part = buffer.toString().trim(); - if (part.isNotEmpty) { - parts.add(part); - } - buffer.clear(); - continue; - } - } - - buffer.write(char); - } - - // Add the last part - final lastPart = buffer.toString().trim(); - if (lastPart.isNotEmpty) { - parts.add(lastPart); - } - - return parts; - } - - /// Tries to parse field values as lists and generate list diffs - String? _tryParseFieldAsListDiff(String prevValue, String currValue) { - // Check if both values look like lists [...] - if (!prevValue.startsWith('[') || - !prevValue.endsWith(']') || - !currValue.startsWith('[') || - !currValue.endsWith(']')) { - return null; - } - - // Parse the list contents - final prevList = _parseListString(prevValue); - final currList = _parseListString(currValue); - - if (prevList == null || currList == null) return null; - - return _generateListDiff(prevList, currList); - } - - /// Parses a list string like "[item1, item2, item3]" into a List - List? _parseListString(String listStr) { - if (!listStr.startsWith('[') || !listStr.endsWith(']')) return null; - - final content = listStr.substring(1, listStr.length - 1).trim(); - if (content.isEmpty) return []; - - // Split by comma, handling nested structures - return _splitFields(content); - } - - /// Truncates long strings to avoid excessive logging - String _truncateIfNeeded(String text, {int maxLength = 700}) { - if (text.length <= maxLength) { - return text; - } - return '${text.substring(0, maxLength)}... ' - '(${text.length - maxLength} more characters)'; + // Provider update logging disabled to reduce noise } } diff --git a/lib/src/core/utils/oauth_resolver.dart b/lib/src/core/utils/oauth_resolver.dart index 48f99e5d..c16ce7e6 100644 --- a/lib/src/core/utils/oauth_resolver.dart +++ b/lib/src/core/utils/oauth_resolver.dart @@ -32,15 +32,12 @@ import 'package:spark/src/core/utils/did_utils.dart'; /// ```dart /// // From PDS URL /// final authServer = await resolveOAuthServer('https://suillus.us-west.host.bsky.network'); -/// print('Authorization server: $authServer'); // 'bsky.social' /// /// // From handle /// final authServer = await resolveOAuthServer('user.bsky.social'); -/// print('Authorization server: $authServer'); // 'bsky.social' /// /// // From DID /// final authServer = await resolveOAuthServer('did:plc:abc123'); -/// print('Authorization server: $authServer'); // 'bsky.social' /// ``` Future resolveOAuthServer( String input, { diff --git a/lib/src/features/auth/providers/auth_providers.dart b/lib/src/features/auth/providers/auth_providers.dart index a79f6db4..74b4eb70 100644 --- a/lib/src/features/auth/providers/auth_providers.dart +++ b/lib/src/features/auth/providers/auth_providers.dart @@ -74,8 +74,6 @@ class Auth extends _$Auth { /// /// Returns the authorization URL that the user should be redirected to Future initiateOAuth(String handle) async { - _logger.i('Initiating OAuth for handle: $handle'); - state = state.copyWith(isLoading: true, error: null); try { @@ -98,8 +96,6 @@ class Auth extends _$Auth { /// /// Returns the authorization URL that the user should be redirected to Future initiateOAuthWithService(String service) async { - _logger.i('Initiating OAuth with service: $service'); - state = state.copyWith(isLoading: true, error: null); try { @@ -129,8 +125,6 @@ class Auth extends _$Auth { /// /// Returns the result of the login attempt Future completeOAuth(String callbackUrl) async { - _logger.i('Completing OAuth with callback'); - try { final result = await _authRepository.completeOAuth(callbackUrl); @@ -157,7 +151,6 @@ class Auth extends _$Auth { /// Logs out the current user Future logout() async { - _logger.i('Logout attempt by service layer'); state = state.copyWith(isLoading: true, error: null); try { @@ -176,8 +169,6 @@ class Auth extends _$Auth { /// Validates if the current session is still active /// Returns true if valid, false otherwise Future validateSession() async { - _logger.d('Session validation by service layer'); - try { final result = await _authRepository.validateSession(); _updateState(); @@ -192,8 +183,6 @@ class Auth extends _$Auth { /// Refreshes the authentication token /// Returns true if the session was successfully refreshed Future refreshToken() async { - _logger.i('Token refresh by service layer'); - try { final result = await _authRepository.refreshToken(); _updateState(); @@ -220,9 +209,6 @@ class Auth extends _$Auth { } else { // Permission not granted yet, defer until main screen _pendingPushRegistration = true; - _logger.i( - 'Push permission not granted, deferring registration to main screen', - ); } } catch (e, stackTrace) { // Don't fail login if push registration fails @@ -245,13 +231,10 @@ class Auth extends _$Auth { platform: pushService.platform, appId: 'so.sprk.app', ); - _logger.i('Push notifications registered successfully'); // Set up listener for token refresh await _setupTokenRefreshListener(pushService, notificationRepo); _pendingPushRegistration = false; - } else { - _logger.w('No push token available'); } } @@ -262,7 +245,6 @@ class Auth extends _$Auth { /// Call this from the main screen after login Future requestPushPermissionAndRegister() async { if (!_pendingPushRegistration) { - _logger.d('No pending push registration'); return true; } @@ -274,7 +256,6 @@ class Auth extends _$Auth { await _doRegisterPush(pushService); return true; } else { - _logger.w('User denied push notification permission'); _pendingPushRegistration = false; return false; } @@ -298,14 +279,12 @@ class Auth extends _$Auth { _tokenRefreshSubscription = pushService.onTokenRefresh.listen( (newToken) async { - _logger.i('FCM token refreshed, re-registering push notifications'); try { await notificationRepo.registerPush( token: newToken, platform: pushService.platform, appId: 'so.sprk.app', ); - _logger.i('Push notifications re-registered with new token'); } catch (e, stackTrace) { _logger.e( 'Failed to re-register push notifications after token refresh', @@ -341,7 +320,6 @@ class Auth extends _$Auth { platform: pushService.platform, appId: 'so.sprk.app', ); - _logger.i('Push notifications unregistered successfully'); } } catch (e, stackTrace) { // Don't fail logout if push unregistration fails diff --git a/lib/src/features/auth/ui/pages/onboarding_page.dart b/lib/src/features/auth/ui/pages/onboarding_page.dart index 1c3d1b5e..cfbffe30 100644 --- a/lib/src/features/auth/ui/pages/onboarding_page.dart +++ b/lib/src/features/auth/ui/pages/onboarding_page.dart @@ -96,9 +96,8 @@ class _OnboardingPageState extends ConsumerState { // Navigate to main screen context.router.replaceAll([const MainRoute()]); - } catch (e) { + } catch (_) { if (!mounted) return; - // Error handling - snackbar removed } finally { if (mounted) { setState(() { diff --git a/lib/src/features/feed/providers/feed_provider.dart b/lib/src/features/feed/providers/feed_provider.dart index 49b3df1a..19a47cbe 100644 --- a/lib/src/features/feed/providers/feed_provider.dart +++ b/lib/src/features/feed/providers/feed_provider.dart @@ -45,11 +45,6 @@ class FeedNotifier extends _$FeedNotifier { _logger = GetIt.instance().getLogger( 'FeedNotifier ${feed.config.id}', ); - } else { - _logger.d( - 'Build called again for ${feed.config.id}, ' - 'hasBeenBuilt: $_hasBeenBuilt', - ); } listenSelf((previous, next) { @@ -97,13 +92,11 @@ class FeedNotifier extends _$FeedNotifier { Future loadAndUpdateFirstLoad() async { if (_isLoadingInProgress || state.loadingFirstLoad) { - _logger.w('Load already in progress, skipping duplicate call'); return; } if (_lastErrorTime != null && DateTime.now().difference(_lastErrorTime!) < _errorCooldown) { - _logger.w('In error cooldown, skipping load'); return; } @@ -147,7 +140,6 @@ class FeedNotifier extends _$FeedNotifier { // We just need to merge them with self-labels and process them final allLabels =