diff --git a/.cursor/rules/logging.mdc b/.cursor/rules/logging.mdc index 77b1b2f5..7d8c4b11 100644 --- a/.cursor/rules/logging.mdc +++ b/.cursor/rules/logging.mdc @@ -18,7 +18,7 @@ A lightweight, flexible logging system for Spark Social with multiple outputs an ```dart -import 'package:sparksocial/src/core/utils/logging/logger.dart'; +import 'package:spark/src/core/utils/logging/logger.dart'; // Get a logger final logger = GetIt.instance().getLogger('MyFeature'); diff --git a/analysis_options.yaml b/analysis_options.yaml index 02843b9e..63f8f40f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,8 +2,6 @@ include: - package:very_good_analysis/analysis_options.yaml - package:flutter_lints/flutter.yaml -formatter: - page_width: 130 analyzer: errors: avoid_catches_without_on_clauses: ignore @@ -12,7 +10,6 @@ analyzer: inference_failure_on_function_invocation: ignore inference_failure_on_instance_creation: ignore invalid_annotation_target: ignore - lines_longer_than_80_chars: ignore comment_references: ignore # enable when starting documentation public_member_api_docs: ignore # enable when starting documentation sort_pub_dependencies: ignore @@ -28,7 +25,6 @@ analyzer: unawaited_futures: ignore avoid_positional_boolean_parameters: ignore use_setters_to_change_properties: ignore - prefer_asserts_with_message: ignore document_ignores: ignore one_member_abstracts: ignore avoid_bool_literals_in_conditional_expressions: ignore diff --git a/lib/main.dart b/lib/main.dart index 3b6cfca3..d89797ec 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,19 +4,22 @@ import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod; import 'package:fvp/fvp.dart' as fvp; -import 'package:sparksocial/src/core/auth/data/repositories/auth_repository.dart'; -import 'package:sparksocial/src/core/auth/data/repositories/auth_repository_impl.dart'; -import 'package:sparksocial/src/core/di/service_locator.dart'; -import 'package:sparksocial/src/core/ui/theme/data/models/app_theme.dart'; -import 'package:sparksocial/src/core/utils/logging/logging.dart'; -import 'package:sparksocial/src/core/utils/logging/riverpod_logger.dart'; -import 'package:sparksocial/src/sprk_app.dart'; +import 'package:spark/src/core/auth/data/repositories/auth_repository.dart'; +import 'package:spark/src/core/auth/data/repositories/auth_repository_impl.dart'; +import 'package:spark/src/core/di/service_locator.dart'; +import 'package:spark/src/core/ui/theme/data/models/app_theme.dart'; +import 'package:spark/src/core/utils/logging/logging.dart'; +import 'package:spark/src/core/utils/logging/riverpod_logger.dart'; +import 'package:spark/src/sprk_app.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await dotenv.load(); - SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); // Force dark status bar and navigation bar SystemChrome.setSystemUIOverlayStyle(AppTheme.darkSystemUiStyle); @@ -32,8 +35,15 @@ void main() async { await _initializeAuth(); // Create a ProviderContainer with the Riverpod logger - final container = riverpod.ProviderContainer(observers: [SparkRiverpodLogger()]); - runApp(riverpod.UncontrolledProviderScope(container: container, child: const SprkApp())); + final container = riverpod.ProviderContainer( + observers: [SparkRiverpodLogger()], + ); + runApp( + riverpod.UncontrolledProviderScope( + container: container, + child: const SprkApp(), + ), + ); } /// Setup logging framework based on environment diff --git a/lib/src/core/auth/data/models/identity_info.dart b/lib/src/core/auth/data/models/identity_info.dart index 0953d028..4f925d6c 100644 --- a/lib/src/core/auth/data/models/identity_info.dart +++ b/lib/src/core/auth/data/models/identity_info.dart @@ -17,7 +17,8 @@ abstract class IdentityInfo with _$IdentityInfo { Map? didDocument, }) = _IdentityInfo; - factory IdentityInfo.fromJson(Map json) => _$IdentityInfoFromJson(json); + factory IdentityInfo.fromJson(Map json) => + _$IdentityInfoFromJson(json); } /// Represents the state of identity resolution operations diff --git a/lib/src/core/auth/data/models/login_result.dart b/lib/src/core/auth/data/models/login_result.dart index 6f472422..0c62d174 100644 --- a/lib/src/core/auth/data/models/login_result.dart +++ b/lib/src/core/auth/data/models/login_result.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:sparksocial/src/core/auth/data/models/login_status.dart'; +import 'package:spark/src/core/auth/data/models/login_status.dart'; part 'login_result.freezed.dart'; @@ -12,9 +12,12 @@ abstract class LoginResult with _$LoginResult { }) = _LoginResult; const LoginResult._(); - factory LoginResult.success() => const LoginResult(status: LoginStatus.success); - factory LoginResult.failed(String error) => LoginResult(status: LoginStatus.failed, error: error); - factory LoginResult.codeRequired(String error) => LoginResult(status: LoginStatus.codeRequired, error: error); + factory LoginResult.success() => + const LoginResult(status: LoginStatus.success); + factory LoginResult.failed(String error) => + LoginResult(status: LoginStatus.failed, error: error); + factory LoginResult.codeRequired(String error) => + LoginResult(status: LoginStatus.codeRequired, error: error); bool get isSuccess => status == LoginStatus.success; bool get isCodeRequired => status == LoginStatus.codeRequired; diff --git a/lib/src/core/auth/data/models/messages_auth_models.dart b/lib/src/core/auth/data/models/messages_auth_models.dart index 72d26b8c..e87114bb 100644 --- a/lib/src/core/auth/data/models/messages_auth_models.dart +++ b/lib/src/core/auth/data/models/messages_auth_models.dart @@ -5,14 +5,23 @@ part 'messages_auth_models.g.dart'; @freezed abstract class MessagesUser with _$MessagesUser { - factory MessagesUser({required String did, required String handle, String? displayName}) = _MessagesUser; - factory MessagesUser.fromJson(Map json) => _$MessagesUserFromJson(json); + factory MessagesUser({ + required String did, + required String handle, + String? displayName, + }) = _MessagesUser; + factory MessagesUser.fromJson(Map json) => + _$MessagesUserFromJson(json); } @freezed abstract class MessagesAuthResponse with _$MessagesAuthResponse { - factory MessagesAuthResponse({required String accessToken, required String refreshToken, required MessagesUser user}) = - _MessagesAuthResponse; + factory MessagesAuthResponse({ + required String accessToken, + required String refreshToken, + required MessagesUser user, + }) = _MessagesAuthResponse; - factory MessagesAuthResponse.fromJson(Map json) => _$MessagesAuthResponseFromJson(json); + factory MessagesAuthResponse.fromJson(Map json) => + _$MessagesAuthResponseFromJson(json); } diff --git a/lib/src/core/auth/data/repositories/auth_repository.dart b/lib/src/core/auth/data/repositories/auth_repository.dart index fd9e6970..9eec4423 100644 --- a/lib/src/core/auth/data/repositories/auth_repository.dart +++ b/lib/src/core/auth/data/repositories/auth_repository.dart @@ -1,6 +1,6 @@ import 'package:atproto/atproto.dart'; import 'package:atproto/core.dart'; -import 'package:sparksocial/src/core/auth/data/models/login_result.dart'; +import 'package:spark/src/core/auth/data/models/login_result.dart'; /// Authentication repository interface for AT Protocol abstract class AuthRepository { @@ -27,8 +27,12 @@ abstract class AuthRepository { /// [password] - The user password /// [inviteCode] - Optional invite code for restricted registrations /// - /// Returns a tuple with a boolean indicating success and an optional error message - Future<({bool success, String? error})> register(String handle, String email, String password, String? inviteCode); + Future<({bool success, String? error})> register( + String handle, + String email, + String password, + String? inviteCode, + ); /// Logs out the current user Future logout(); 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 6bc7754e..b20ab67c 100644 --- a/lib/src/core/auth/data/repositories/auth_repository_impl.dart +++ b/lib/src/core/auth/data/repositories/auth_repository_impl.dart @@ -5,11 +5,11 @@ import 'package:atproto/atproto.dart'; import 'package:atproto/core.dart'; import 'package:get_it/get_it.dart'; import 'package:http/http.dart' as http; -import 'package:sparksocial/src/core/auth/data/models/login_result.dart'; -import 'package:sparksocial/src/core/auth/data/repositories/auth_repository.dart'; -import 'package:sparksocial/src/core/storage/storage.dart'; -import 'package:sparksocial/src/core/utils/logging/log_service.dart'; -import 'package:sparksocial/src/core/utils/logging/logger.dart'; +import 'package:spark/src/core/auth/data/models/login_result.dart'; +import 'package:spark/src/core/auth/data/repositories/auth_repository.dart'; +import 'package:spark/src/core/storage/storage.dart'; +import 'package:spark/src/core/utils/logging/log_service.dart'; +import 'package:spark/src/core/utils/logging/logger.dart'; /// Implementation of the authentication repository for AT Protocol class AuthRepositoryImpl implements AuthRepository { @@ -20,7 +20,9 @@ class AuthRepositoryImpl implements AuthRepository { Session? _session; ATProto? _atProto; - final SparkLogger _logger = GetIt.instance().getLogger('AuthRepository'); + final SparkLogger _logger = GetIt.instance().getLogger( + 'AuthRepository', + ); final Completer _initCompleter = Completer(); Future get initializationComplete => _initCompleter.future; @@ -61,24 +63,33 @@ class AuthRepositoryImpl implements AuthRepository { return null; } - return pdsUrl.replaceFirst('http://', '').replaceFirst('https://', '').replaceFirst('/', ''); + return pdsUrl + .replaceFirst('http://', '') + .replaceFirst('https://', '') + .replaceFirst('/', ''); } bool _isTokenExpired(Jwt token) { - return DateTime.now().isAfter(token.exp.subtract(const Duration(minutes: 5))); + return DateTime.now().isAfter( + token.exp.subtract(const Duration(minutes: 5)), + ); } Future _loadSavedSession() async { try { _logger.d('Loading saved session'); - final savedSessionJson = await StorageManager.instance.secure.getString(StorageKeys.userSession); + final savedSessionJson = await StorageManager.instance.secure.getString( + StorageKeys.userSession, + ); if (savedSessionJson == null) { _logger.d('No saved session found'); return; } - _session = Session.fromJson(json.decode(savedSessionJson) as Map); + _session = Session.fromJson( + json.decode(savedSessionJson) as Map, + ); if (_session == null) { _logger.w('Failed to parse saved session'); return; @@ -105,13 +116,19 @@ class AuthRepositoryImpl implements AuthRepository { } _logger.i('Refreshing session for user: ${_session!.handle}'); - var service = _session!.didDoc != null ? _extractPdsDomain(_session!.didDoc!) : null; + var service = _session!.didDoc != null + ? _extractPdsDomain(_session!.didDoc!) + : null; if (service == null) { _logger.d('Fetching DID document from PLC directory'); - final didDocResponse = await http.get(Uri.parse('https://plc.directory/${_session!.did}')); + final didDocResponse = await http.get( + Uri.parse('https://plc.directory/${_session!.did}'), + ); if (didDocResponse.statusCode == 200) { - service = _extractPdsDomain(json.decode(didDocResponse.body) as Map); + service = _extractPdsDomain( + json.decode(didDocResponse.body) as Map, + ); } } @@ -121,7 +138,10 @@ class AuthRepositoryImpl implements AuthRepository { } _logger.d('Using service endpoint: $service'); - final response = await refreshSession(service: service, refreshJwt: _session!.refreshJwt); + final response = await refreshSession( + service: service, + refreshJwt: _session!.refreshJwt, + ); if (response.status != HttpStatus.ok) { _logger.e('Failed to refresh session: ${response.status}'); @@ -145,7 +165,10 @@ class AuthRepositoryImpl implements AuthRepository { try { _logger.d('Saving session for user: ${sessionData.handle}'); final sessionJson = sessionData.toJson(); - await StorageManager.instance.secure.setString(StorageKeys.userSession, json.encode(sessionJson)); + await StorageManager.instance.secure.setString( + StorageKeys.userSession, + json.encode(sessionJson), + ); _logger.d('Session saved successfully'); } catch (e) { _logger.e('Failed to save session', error: e); @@ -165,7 +188,11 @@ class AuthRepositoryImpl implements AuthRepository { } @override - Future login(String handle, String password, {String? authCode}) async { + Future login( + String handle, + String password, { + String? authCode, + }) async { try { _logger.i('Login attempt for user: $handle'); final at = ATProto.anonymous(service: 'pds.sprk.so'); @@ -175,17 +202,24 @@ class AuthRepositoryImpl implements AuthRepository { _logger ..d('Resolved DID: $did') ..d('Fetching DID document'); - final didDocResponse = await http.get(Uri.parse('https://plc.directory/$did')); + final didDocResponse = await http.get( + Uri.parse('https://plc.directory/$did'), + ); if (didDocResponse.statusCode != 200) { _logger.e('Failed to fetch DID document: ${didDocResponse.statusCode}'); - throw Exception('Failed to fetch DID document: ${didDocResponse.statusCode}'); + throw Exception( + 'Failed to fetch DID document: ${didDocResponse.statusCode}', + ); } final didDoc = json.decode(didDocResponse.body); final pdsUrl = - (didDoc['service'] as List).firstWhere((s) => s['id'] == '#atproto_pds', orElse: () => '')['serviceEndpoint'] + (didDoc['service'] as List).firstWhere( + (s) => s['id'] == '#atproto_pds', + orElse: () => '', + )['serviceEndpoint'] as String?; if (pdsUrl == null) { @@ -222,7 +256,9 @@ class AuthRepositoryImpl implements AuthRepository { e.toString().contains('sign-in code') || e.toString().contains('verification code'))) { _logger.i('Authentication code required for user: $handle'); - return LoginResult.codeRequired('Authentication code required. Check your email for a verification code.'); + return LoginResult.codeRequired( + 'Authentication code required. Check your email for a code.', + ); } rethrow; } @@ -233,7 +269,12 @@ class AuthRepositoryImpl implements AuthRepository { } @override - Future<({bool success, String? error})> register(String handle, String email, String password, String? inviteCode) async { + Future<({bool success, String? error})> register( + String handle, + String email, + String password, + String? inviteCode, + ) async { try { _logger.i('Registration attempt for handle: $handle, email: $email'); final at = ATProto.anonymous(service: 'pds.sprk.so'); diff --git a/lib/src/core/auth/data/repositories/identity_repository_impl.dart b/lib/src/core/auth/data/repositories/identity_repository_impl.dart index e608c580..2f9916a7 100644 --- a/lib/src/core/auth/data/repositories/identity_repository_impl.dart +++ b/lib/src/core/auth/data/repositories/identity_repository_impl.dart @@ -5,10 +5,10 @@ import 'package:atproto/atproto.dart'; import 'package:get_it/get_it.dart'; import 'package:http/http.dart' as http; -import 'package:sparksocial/src/core/auth/data/repositories/identity_repository.dart'; -import 'package:sparksocial/src/core/storage/storage.dart'; -import 'package:sparksocial/src/core/utils/logging/log_service.dart'; -import 'package:sparksocial/src/core/utils/logging/logger.dart'; +import 'package:spark/src/core/auth/data/repositories/identity_repository.dart'; +import 'package:spark/src/core/storage/storage.dart'; +import 'package:spark/src/core/utils/logging/log_service.dart'; +import 'package:spark/src/core/utils/logging/logger.dart'; /// Implementation of [IdentityRepository] with caching capabilities class IdentityRepositoryImpl implements IdentityRepository { @@ -23,19 +23,30 @@ class IdentityRepositoryImpl implements IdentityRepository { static const Duration _cacheExpiration = Duration(hours: 2); final StorageManager _storageManager; - final SparkLogger _logger = GetIt.instance().getLogger('IdentityRepository'); + final SparkLogger _logger = GetIt.instance().getLogger( + 'IdentityRepository', + ); Future _loadCache() async { try { - final cacheTtl = await _storageManager.preferences.getInt(StorageKeys.identityCacheTtl); - if (cacheTtl != null && DateTime.now().millisecondsSinceEpoch > cacheTtl) { + final cacheTtl = await _storageManager.preferences.getInt( + StorageKeys.identityCacheTtl, + ); + if (cacheTtl != null && + DateTime.now().millisecondsSinceEpoch > cacheTtl) { await clearCache(); return; } - final didToHandleJson = await _storageManager.preferences.getString(StorageKeys.didToHandleCache); - final handleToDidJson = await _storageManager.preferences.getString(StorageKeys.handleToDidCache); - final didDocJson = await _storageManager.preferences.getString(StorageKeys.didDocCache); + final didToHandleJson = await _storageManager.preferences.getString( + StorageKeys.didToHandleCache, + ); + final handleToDidJson = await _storageManager.preferences.getString( + StorageKeys.handleToDidCache, + ); + final didDocJson = await _storageManager.preferences.getString( + StorageKeys.didDocCache, + ); if (didToHandleJson != null) { _loadDidToHandleCache(didToHandleJson); @@ -94,11 +105,20 @@ class IdentityRepositoryImpl implements IdentityRepository { DateTime.now().add(_cacheExpiration).millisecondsSinceEpoch, ); - await _storageManager.preferences.setString(StorageKeys.didToHandleCache, json.encode(_didToHandleCache)); + await _storageManager.preferences.setString( + StorageKeys.didToHandleCache, + json.encode(_didToHandleCache), + ); - await _storageManager.preferences.setString(StorageKeys.handleToDidCache, json.encode(_handleToDidCache)); + await _storageManager.preferences.setString( + StorageKeys.handleToDidCache, + json.encode(_handleToDidCache), + ); - await _storageManager.preferences.setString(StorageKeys.didDocCache, json.encode(_didDocCache)); + await _storageManager.preferences.setString( + StorageKeys.didDocCache, + json.encode(_didDocCache), + ); } catch (e) { _logger.e('Error saving identity cache', error: e); } @@ -182,7 +202,9 @@ class IdentityRepositoryImpl implements IdentityRepository { final response = await http.get(url); if (response.statusCode != 200) { - _logger.w('Failed to resolve DID document. Status: ${response.statusCode}'); + _logger.w( + 'Failed to resolve DID document. Status: ${response.statusCode}', + ); return null; } @@ -261,7 +283,9 @@ class IdentityRepositoryImpl implements IdentityRepository { } @override - Future> resolveHandlesToDids(List handles) async { + Future> resolveHandlesToDids( + List handles, + ) async { final results = {}; final futures = >[]; diff --git a/lib/src/core/auth/data/repositories/onboarding_repository.dart b/lib/src/core/auth/data/repositories/onboarding_repository.dart index bdbbb65d..2d9ae776 100644 --- a/lib/src/core/auth/data/repositories/onboarding_repository.dart +++ b/lib/src/core/auth/data/repositories/onboarding_repository.dart @@ -1,5 +1,5 @@ import 'package:bluesky/app_bsky_actor_profile.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/graph_models.dart'; +import 'package:spark/src/core/network/atproto/data/models/graph_models.dart'; abstract class OnboardingRepository { /// Checks if the current user has a Spark profile @@ -9,7 +9,11 @@ abstract class OnboardingRepository { Future getBskyProfile(); /// Creates a Spark actor profile with custom values - Future createSparkProfile({required String displayName, required String description, dynamic avatar}); + Future createSparkProfile({ + required String displayName, + required String description, + dynamic avatar, + }); /// Fetches the list of DIDs that the user follows on Bluesky Future getBskyFollows({String? cursor}); diff --git a/lib/src/core/auth/data/repositories/onboarding_repository_impl.dart b/lib/src/core/auth/data/repositories/onboarding_repository_impl.dart index f21c120d..68e95976 100644 --- a/lib/src/core/auth/data/repositories/onboarding_repository_impl.dart +++ b/lib/src/core/auth/data/repositories/onboarding_repository_impl.dart @@ -5,21 +5,25 @@ import 'package:atproto/core.dart'; import 'package:bluesky/app_bsky_actor_profile.dart'; import 'package:bluesky/bluesky.dart' as bs; import 'package:get_it/get_it.dart'; -import 'package:sparksocial/src/core/auth/data/repositories/auth_repository.dart'; -import 'package:sparksocial/src/core/auth/data/repositories/onboarding_repository.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/actor_models.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/graph_models.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/repo_repository.dart'; -import 'package:sparksocial/src/core/utils/logging/log_service.dart'; -import 'package:sparksocial/src/core/utils/logging/logger.dart'; +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/data/models/actor_models.dart'; +import 'package:spark/src/core/network/atproto/data/models/graph_models.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/repo_repository.dart'; +import 'package:spark/src/core/utils/logging/log_service.dart'; +import 'package:spark/src/core/utils/logging/logger.dart'; class OnboardingRepositoryImpl implements OnboardingRepository { - OnboardingRepositoryImpl({required RepoRepository repoRepository, required AuthRepository authRepository}) - : _repoRepository = repoRepository, - _authRepository = authRepository; + OnboardingRepositoryImpl({ + required RepoRepository repoRepository, + required AuthRepository authRepository, + }) : _repoRepository = repoRepository, + _authRepository = authRepository; final RepoRepository _repoRepository; final AuthRepository _authRepository; - final SparkLogger _logger = GetIt.instance().getLogger('OnboardingRepository'); + final SparkLogger _logger = GetIt.instance().getLogger( + 'OnboardingRepository', + ); Session? get _session => _authRepository.session; ATProto? get _atproto => _authRepository.atproto; @@ -36,7 +40,9 @@ class OnboardingRepositoryImpl implements OnboardingRepository { } catch (e) { // Treat 404 and 'Could not locate record' 400 errors as no profile final msg = e.toString().toLowerCase(); - if (msg.contains('404') || msg.contains('could not locate record') || msg.contains('400')) { + if (msg.contains('404') || + msg.contains('could not locate record') || + msg.contains('400')) { return false; } _logger.e('Error checking Spark profile', error: e); @@ -49,7 +55,9 @@ class OnboardingRepositoryImpl implements OnboardingRepository { if (_session == null) return null; try { - final uri = AtUri.parse('at://${_session!.did}/app.bsky.actor.profile/self'); + final uri = AtUri.parse( + 'at://${_session!.did}/app.bsky.actor.profile/self', + ); final response = await _repoRepository.getRecord(uri: uri); return ActorProfileRecord.fromJson(response.record.toJson()); } catch (e) { @@ -59,7 +67,11 @@ class OnboardingRepositoryImpl implements OnboardingRepository { } @override - Future createSparkProfile({required String displayName, required String description, dynamic avatar}) async { + Future createSparkProfile({ + required String displayName, + required String description, + dynamic avatar, + }) async { dynamic avatarField; // If the avatar is raw bytes, upload it as a blob first to avoid sending @@ -75,7 +87,8 @@ class OnboardingRepositoryImpl implements OnboardingRepository { rethrow; } } else { - // Avatar is already a blob (e.g., imported from Bluesky). Try to serialise if possible. + // Avatar is already a blob (e.g., imported from Bluesky). + // Try to serialise if possible. try { // Many blob classes expose toJson(). If not, fall back to raw value. final toJson = (avatar as dynamic).toJson; @@ -108,7 +121,11 @@ class OnboardingRepositoryImpl implements OnboardingRepository { final bsky = bs.Bluesky.fromSession(_session!); final did = _session!.did; - final response = await bsky.graph.getFollows(actor: did, limit: 100, cursor: cursor); + final response = await bsky.graph.getFollows( + actor: did, + limit: 100, + cursor: cursor, + ); // Convert raw data to our structured model final rawData = response.data.toJson(); @@ -116,11 +133,15 @@ class OnboardingRepositoryImpl implements OnboardingRepository { final follows = rawFollows .map( - (followData) => ProfileView.fromJson(followData as Map), + (followData) => + ProfileView.fromJson(followData as Map), ) .toList(); - return FollowsResponse(follows: follows, cursor: rawData['cursor'] as String?); + return FollowsResponse( + follows: follows, + cursor: rawData['cursor'] as String?, + ); } @override @@ -131,7 +152,10 @@ class OnboardingRepositoryImpl implements OnboardingRepository { 'createdAt': DateTime.now().toUtc().toIso8601String(), }; - final response = await _repoRepository.createRecord(collection: 'so.sprk.graph.follow', record: record); + final response = await _repoRepository.createRecord( + collection: 'so.sprk.graph.follow', + record: record, + ); if (response.uri.toString().isEmpty) { throw Exception('Failed to create Spark follow'); diff --git a/lib/src/core/config/app_config.dart b/lib/src/core/config/app_config.dart index 14542e23..1af0c3cf 100644 --- a/lib/src/core/config/app_config.dart +++ b/lib/src/core/config/app_config.dart @@ -2,25 +2,28 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; /// AppConfig manages application-wide configuration settings /// loaded from environment variables or .env files. -/// Not to be confused with the settings feature, which is a separate feature that manages user settings. /// /// All app configuration should be accessed through this class /// to maintain a single source of truth for application settings. class AppConfig { /// Base URL for the video processing service. - static String get videoServiceUrl => _getStringValue('VIDEO_SERVICE_URL', 'http://localhost:3000'); + static String get videoServiceUrl => + _getStringValue('VIDEO_SERVICE_URL', 'http://localhost:3000'); /// License key for the img.ly editor. static String get license => _getStringValue('SHOWCASES_LICENSE_FLUTTER', ''); /// URL for the app view (web view display). - static String get appViewUrl => _getStringValue('SPRK_APPVIEW_URL', 'http://localhost:3000'); + static String get appViewUrl => + _getStringValue('SPRK_APPVIEW_URL', 'http://localhost:3000'); /// Base URL for the messages service (chat service). - static String get messagesServiceUrl => _getStringValue('MESSAGES_SERVICE_URL', 'http://localhost:3000'); + static String get messagesServiceUrl => + _getStringValue('MESSAGES_SERVICE_URL', 'http://localhost:3000'); /// Service DID for the chat service (used for service auth). - static String get chatServiceDid => _getStringValue('CHAT_SERVICE_DID', 'did:web:chat.sprk.so'); + static String get chatServiceDid => + _getStringValue('CHAT_SERVICE_DID', 'did:web:chat.sprk.so'); /// Whether new user registrations are disabled. static bool get signupsDisabled => _getBoolValue('SIGNUPS_DISABLED', false); @@ -29,10 +32,12 @@ class AppConfig { static int get apiTimeoutSeconds => _getIntValue('API_TIMEOUT_SECONDS', 30); /// Maximum upload file size in MB. - static double get maxUploadSizeMB => _getDoubleValue('MAX_UPLOAD_SIZE_MB', 100); + static double get maxUploadSizeMB => + _getDoubleValue('MAX_UPLOAD_SIZE_MB', 100); /// The current application environment (development, production, etc.) - static String get environment => _getStringValue('ENVIRONMENT', 'development'); + static String get environment => + _getStringValue('ENVIRONMENT', 'development'); /// Checks if the app is running in development mode. static bool get isDevelopment => environment == 'development'; diff --git a/lib/src/core/design_system/components/atoms/avatar_stack.dart b/lib/src/core/design_system/components/atoms/avatar_stack.dart index 931e424f..4e67c286 100644 --- a/lib/src/core/design_system/components/atoms/avatar_stack.dart +++ b/lib/src/core/design_system/components/atoms/avatar_stack.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/ui/widgets/user_avatar.dart'; +import 'package:spark/src/core/ui/widgets/user_avatar.dart'; /// Data class representing an avatar in the stack. class AvatarData { diff --git a/lib/src/core/design_system/components/atoms/buttons/accent_button.dart b/lib/src/core/design_system/components/atoms/buttons/accent_button.dart index cc35c3a6..bdb36372 100644 --- a/lib/src/core/design_system/components/atoms/buttons/accent_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/accent_button.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class AccentButton extends StatelessWidget { final String label; @@ -21,7 +21,10 @@ class AccentButton extends StatelessWidget { onTap: onPressed, borderRadius: BorderRadius.circular(500), child: Container( - constraints: BoxConstraints.tightFor(height: 30, width: label.length * 10 + 20), + constraints: BoxConstraints.tightFor( + height: 30, + width: label.length * 10 + 20, + ), height: 30, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( diff --git a/lib/src/core/design_system/components/atoms/buttons/action_button.dart b/lib/src/core/design_system/components/atoms/buttons/action_button.dart index 02e2e200..786af677 100644 --- a/lib/src/core/design_system/components/atoms/buttons/action_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/action_button.dart @@ -1,9 +1,9 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class ActionButton extends StatelessWidget { final Widget icon; @@ -53,7 +53,9 @@ class ActionButton extends StatelessWidget { shape: BoxShape.circle, ) : BoxDecoration( - color: isDark ? AppColors.darkGreyButton : AppColors.lightGreyButton, + color: isDark + ? AppColors.darkGreyButton + : AppColors.lightGreyButton, shape: BoxShape.circle, border: const Border.fromBorderSide( BorderSide( diff --git a/lib/src/core/design_system/components/atoms/buttons/app_leading_button.dart b/lib/src/core/design_system/components/atoms/buttons/app_leading_button.dart index 1485c07d..856518d9 100644 --- a/lib/src/core/design_system/components/atoms/buttons/app_leading_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/app_leading_button.dart @@ -1,6 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; /// Design System leading button that leverages AutoLeadingButton for /// smart back/close/drawer behavior while keeping Spark's visual style. @@ -16,19 +16,24 @@ class AppLeadingButton extends StatelessWidget { @override Widget build(BuildContext context) { return AutoLeadingButton( - builder: (BuildContext context, LeadingType leadingType, VoidCallback? action) { - // If there's nothing to pop/open, render nothing. - if (action == null) return const SizedBox.shrink(); + builder: + ( + BuildContext context, + LeadingType leadingType, + VoidCallback? action, + ) { + // If there's nothing to pop/open, render nothing. + if (action == null) return const SizedBox.shrink(); - final theme = Theme.of(context); - final iconColor = color ?? theme.textTheme.titleLarge?.color; + final theme = Theme.of(context); + final iconColor = color ?? theme.textTheme.titleLarge?.color; - return IconButton( - onPressed: action, - icon: AppIcons.chevronleft(color: iconColor), - tooltip: tooltip ?? 'Back', - ); - }, + return IconButton( + onPressed: action, + icon: AppIcons.chevronleft(color: iconColor), + tooltip: tooltip ?? 'Back', + ); + }, ); } } diff --git a/lib/src/core/design_system/components/atoms/buttons/circle_icon_button.dart b/lib/src/core/design_system/components/atoms/buttons/circle_icon_button.dart index 782fa270..1439d3c8 100644 --- a/lib/src/core/design_system/components/atoms/buttons/circle_icon_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/circle_icon_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; /// A circular icon button component following the design system patterns. /// diff --git a/lib/src/core/design_system/components/atoms/buttons/follow_pill_button.dart b/lib/src/core/design_system/components/atoms/buttons/follow_pill_button.dart index 8c7869ca..eae430fe 100644 --- a/lib/src/core/design_system/components/atoms/buttons/follow_pill_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/follow_pill_button.dart @@ -2,10 +2,10 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class FollowPillButton extends StatelessWidget { const FollowPillButton({ diff --git a/lib/src/core/design_system/components/atoms/buttons/live_button.dart b/lib/src/core/design_system/components/atoms/buttons/live_button.dart index f0912e1e..d45f5b6c 100644 --- a/lib/src/core/design_system/components/atoms/buttons/live_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/live_button.dart @@ -1,7 +1,7 @@ import 'dart:ui'; // Required for ImageFilter import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; class LiveButton extends StatelessWidget { final Widget child; diff --git a/lib/src/core/design_system/components/atoms/buttons/long_button.dart b/lib/src/core/design_system/components/atoms/buttons/long_button.dart index 167095ce..79cedd06 100644 --- a/lib/src/core/design_system/components/atoms/buttons/long_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/long_button.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; /// A general-purpose button with gradient background that can display any text. /// -/// This button features an accent gradient background with a glass stroke border. +/// It has an accent gradient background with a glass stroke border. /// It's commonly used in authentication screens and forms, but can be used /// anywhere a prominent action button is needed. class LongButton extends StatelessWidget { @@ -34,7 +34,10 @@ class LongButton extends StatelessWidget { decoration: BoxDecoration( gradient: AppGradients.accent, borderRadius: BorderRadius.circular(8), - border: const GradientBoxBorder(gradient: AppGradients.glassStroke, width: 3), + border: const GradientBoxBorder( + gradient: AppGradients.glassStroke, + width: 3, + ), ), child: Center( child: Text( diff --git a/lib/src/core/design_system/components/atoms/buttons/primary_button.dart b/lib/src/core/design_system/components/atoms/buttons/primary_button.dart index 251bde2e..6921de89 100644 --- a/lib/src/core/design_system/components/atoms/buttons/primary_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/primary_button.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; /// A general-purpose primary button that can display any text. /// -/// This button uses the primary brand color and is commonly used in -/// authentication screens, but can be used anywhere a prominent primary action is needed. -/// It supports an optional trailing widget (e.g., SVG icon) and customizable minimum size. +/// It uses the primary brand color and is commonly used in authentication +/// screens, but can be used anywhere a prominent primary action is needed. +/// It supports an optional trailing widget (e.g., SVG icon) and customizable +/// minimum size. class PrimaryButton extends StatelessWidget { /// The text to display on the button final String text; diff --git a/lib/src/core/design_system/components/atoms/buttons/tappable_text.dart b/lib/src/core/design_system/components/atoms/buttons/tappable_text.dart index 9f05c693..b0af8e27 100644 --- a/lib/src/core/design_system/components/atoms/buttons/tappable_text.dart +++ b/lib/src/core/design_system/components/atoms/buttons/tappable_text.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class TappableText extends StatelessWidget { final String text; diff --git a/lib/src/core/design_system/components/atoms/buttons/text_button.dart b/lib/src/core/design_system/components/atoms/buttons/text_button.dart index f621d30c..6705c2a0 100644 --- a/lib/src/core/design_system/components/atoms/buttons/text_button.dart +++ b/lib/src/core/design_system/components/atoms/buttons/text_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class TextButton extends StatelessWidget { final String label; diff --git a/lib/src/core/design_system/components/atoms/icons.dart b/lib/src/core/design_system/components/atoms/icons.dart index 54c24c54..3aa67204 100644 --- a/lib/src/core/design_system/components/atoms/icons.dart +++ b/lib/src/core/design_system/components/atoms/icons.dart @@ -5,424 +5,564 @@ import 'package:flutter_svg/flutter_svg.dart'; class AppIcons { static const String _path = 'icons'; - static Widget addPostFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/add_post_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); + static Widget addPostFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/add_post_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget addUser({double size = 24, Color? color}) => SvgPicture.asset( '$_path/add_user.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget add({double size = 24, Color? color}) => SvgPicture.asset( '$_path/add.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget arrowRight({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/arrow_right.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget arrowRight({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/arrow_right.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget at({double size = 24, Color? color}) => SvgPicture.asset( '$_path/at.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget attach({double size = 24, Color? color}) => SvgPicture.asset( '$_path/attach.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget bookmarkFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/bookmark_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget bookmarkOutline({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/bookmark_outline.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget bookmarkFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/bookmark_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget bookmarkOutline({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/bookmark_outline.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget camera({double size = 24, Color? color}) => SvgPicture.asset( '$_path/camera.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget cancel({double size = 24, Color? color}) => SvgPicture.asset( '$_path/cancel.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget chevronDown({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/chevron_down.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget chevronleft({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/chevronleft.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget chevronDown({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/chevron_down.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget chevronleft({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/chevronleft.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget colors({double size = 24, Color? color}) => SvgPicture.asset( '$_path/colors.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget comment({double size = 24, Color? color}) => SvgPicture.asset( '$_path/comment.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget disk({double size = 24, Color? color}) => SvgPicture.asset( '$_path/disk.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget eyeMin({double size = 24, Color? color}) => SvgPicture.asset( '$_path/eye_min.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget filters({double size = 24, Color? color}) => SvgPicture.asset( '$_path/filters.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget folderMini({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/folder_mini.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget folderMini({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/folder_mini.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget gallery({double size = 24, Color? color}) => SvgPicture.asset( '$_path/gallery.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget grid({double size = 24, Color? color}) => SvgPicture.asset( '$_path/grid.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget gridFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/grid_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget gridFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/grid_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget hashtag({double size = 24, Color? color}) => SvgPicture.asset( '$_path/hashtag.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget home({double size = 24, Color? color}) => SvgPicture.asset( '$_path/home.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget homeFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/home_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget homeFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/home_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget less({double size = 24, Color? color}) => SvgPicture.asset( '$_path/less.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget like({double size = 24, Color? color}) => SvgPicture.asset( '$_path/like.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget likeFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/like_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget likeFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/like_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget likeMini({double size = 24, Color? color}) => SvgPicture.asset( '$_path/like_mini.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget link({double size = 24, Color? color}) => SvgPicture.asset( '$_path/link.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget messagesFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/messages_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget messagesFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/messages_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget micro({double size = 24, Color? color}) => SvgPicture.asset( '$_path/micro.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget more({double size = 24, Color? color}) => SvgPicture.asset( '$_path/more.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget moreHoriz({double size = 24, Color? color}) => SvgPicture.asset( '$_path/more_horiz.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget music({double size = 24, Color? color}) => SvgPicture.asset( '$_path/music.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget explore({double size = 24, Color? color}) => SvgPicture.asset( '$_path/explore.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget exploreFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/explore_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget exploreFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/explore_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget messages({double size = 24, Color? color}) => SvgPicture.asset( '$_path/messages.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget navbarPost({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/navbar_post.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget navbarPostFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/navbar_post_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget navbarPost({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/navbar_post.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget navbarPostFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/navbar_post_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget pin({double size = 24, Color? color}) => SvgPicture.asset( '$_path/pin.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget play({double size = 24, Color? color}) => SvgPicture.asset( '$_path/play.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget plusFilled({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/plus_filled.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget plusFilled({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/plus_filled.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget plus({double size = 24, Color? color}) => SvgPicture.asset( '$_path/plus.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget profileCurate({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/profile_curate.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget profileLiked({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/profile_liked.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget profileTagged({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/profile_tagged.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget profileCurate({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/profile_curate.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget profileLiked({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/profile_liked.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); + static Widget profileTagged({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/profile_tagged.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget repost({double size = 24, Color? color}) => SvgPicture.asset( '$_path/repost.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget repostLarge({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/repost_large.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget repostLarge({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/repost_large.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget search({double size = 24, Color? color}) => SvgPicture.asset( '$_path/search.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget send({double size = 24, Color? color}) => SvgPicture.asset( '$_path/send.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget share({double size = 24, Color? color}) => SvgPicture.asset( '$_path/share.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget smiley({double size = 24, Color? color}) => SvgPicture.asset( '$_path/smiley.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget tag({double size = 24, Color? color}) => SvgPicture.asset( '$_path/tag.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget typo({double size = 24, Color? color}) => SvgPicture.asset( '$_path/typo.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget verified({double size = 24, Color? color}) => SvgPicture.asset( '$_path/verified.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget volume({double size = 24, Color? color}) => SvgPicture.asset( '$_path/volume.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget match({double size = 24, Color? color}) => SvgPicture.asset( '$_path/match.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); static Widget sideShare({double size = 24, Color? color}) => SvgPicture.asset( '$_path/side_share.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, - package: 'assets', - ); - static Widget sideCurate({double size = 24, Color? color}) => SvgPicture.asset( - '$_path/side_curate.svg', - width: size, - height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); + static Widget sideCurate({double size = 24, Color? color}) => + SvgPicture.asset( + '$_path/side_curate.svg', + width: size, + height: size, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, + package: 'assets', + ); static Widget gear({double size = 24, Color? color}) => SvgPicture.asset( '$_path/gear.svg', width: size, height: size, - colorFilter: color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, package: 'assets', ); } diff --git a/lib/src/core/design_system/components/atoms/profile_tab_item.dart b/lib/src/core/design_system/components/atoms/profile_tab_item.dart index d786ebd9..f1defdbe 100644 --- a/lib/src/core/design_system/components/atoms/profile_tab_item.dart +++ b/lib/src/core/design_system/components/atoms/profile_tab_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/tab_item.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/design_system/components/atoms/tab_item.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; class ProfileTabItem extends StatelessWidget { const ProfileTabItem({ @@ -21,7 +21,8 @@ class ProfileTabItem extends StatelessWidget { final theme = Theme.of(context); return AppTabItem( activeChild: ShaderMask( - shaderCallback: (bounds) => AppGradients.gradientLinearPrimaryGradient.createShader(bounds), + shaderCallback: (bounds) => + AppGradients.gradientLinearPrimaryGradient.createShader(bounds), blendMode: BlendMode.srcIn, child: filledIcon, ), diff --git a/lib/src/core/design_system/components/atoms/tab_item.dart b/lib/src/core/design_system/components/atoms/tab_item.dart index 59b8f88a..d24b6f27 100644 --- a/lib/src/core/design_system/components/atoms/tab_item.dart +++ b/lib/src/core/design_system/components/atoms/tab_item.dart @@ -40,7 +40,9 @@ class AppTabItem extends StatelessWidget { decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: isSelected ? (indicatorColor ?? theme.colorScheme.primary) : Colors.transparent, + color: isSelected + ? (indicatorColor ?? theme.colorScheme.primary) + : Colors.transparent, width: indicatorThickness, ), ), diff --git a/lib/src/core/design_system/components/atoms/tags/feed_tag.dart b/lib/src/core/design_system/components/atoms/tags/feed_tag.dart index 90607074..b6b36fc0 100644 --- a/lib/src/core/design_system/components/atoms/tags/feed_tag.dart +++ b/lib/src/core/design_system/components/atoms/tags/feed_tag.dart @@ -1,11 +1,17 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class FeedTag extends StatelessWidget { - const FeedTag({required this.id, super.key, this.text = '', this.onTap, this.selected = false}); + const FeedTag({ + required this.id, + super.key, + this.text = '', + this.onTap, + this.selected = false, + }); final String text; final VoidCallback? onTap; @@ -18,7 +24,10 @@ class FeedTag extends StatelessWidget { return InteractivePressable( onTap: onTap, borderRadius: BorderRadius.circular(9), - child: Text(text, style: AppTypography.textMediumThin.copyWith(color: Colors.white70)), + child: Text( + text, + style: AppTypography.textMediumThin.copyWith(color: Colors.white70), + ), ); } return InteractivePressable( @@ -39,7 +48,12 @@ class FeedTag extends StatelessWidget { ), ), child: Center( - child: Text(text, style: AppTypography.textMediumMedium.copyWith(color: Colors.white)), + child: Text( + text, + style: AppTypography.textMediumMedium.copyWith( + color: Colors.white, + ), + ), ), ), ), diff --git a/lib/src/core/design_system/components/atoms/tags/hashtag.dart b/lib/src/core/design_system/components/atoms/tags/hashtag.dart index 1dac28e7..18cab67e 100644 --- a/lib/src/core/design_system/components/atoms/tags/hashtag.dart +++ b/lib/src/core/design_system/components/atoms/tags/hashtag.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class GlassmorphicTag extends StatelessWidget { final String label; diff --git a/lib/src/core/design_system/components/atoms/tags/tag.dart b/lib/src/core/design_system/components/atoms/tags/tag.dart index 9d43fd29..6b98e794 100644 --- a/lib/src/core/design_system/components/atoms/tags/tag.dart +++ b/lib/src/core/design_system/components/atoms/tags/tag.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class Tag extends StatelessWidget { final String mainText; @@ -37,7 +37,12 @@ class Tag extends StatelessWidget { children: [ Text(mainText, style: AppTypography.textMediumMedium), const SizedBox(width: 5), - Text(secondaryText, style: AppTypography.textExtraSmallMedium.copyWith(color: AppColors.grey300)), + Text( + secondaryText, + style: AppTypography.textExtraSmallMedium.copyWith( + color: AppColors.grey300, + ), + ), ], ), ), diff --git a/lib/src/core/design_system/components/atoms/toggles/follow_button.dart b/lib/src/core/design_system/components/atoms/toggles/follow_button.dart index a719b866..fc8c4bd1 100644 --- a/lib/src/core/design_system/components/atoms/toggles/follow_button.dart +++ b/lib/src/core/design_system/components/atoms/toggles/follow_button.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class FollowButton extends StatelessWidget { const FollowButton({ @@ -79,11 +79,15 @@ class FollowButton extends StatelessWidget { height: 36, decoration: isFollowing ? BoxDecoration( - color: isDark ? AppColors.darkGreyButton : AppColors.lightGreyButton, + color: isDark + ? AppColors.darkGreyButton + : AppColors.lightGreyButton, borderRadius: const BorderRadius.all(Radius.circular(8)), border: Border.fromBorderSide( BorderSide( - color: isDark ? AppColors.grey700.withValues(alpha: 0.3) : AppColors.grey100.withValues(alpha: 0.3), + color: isDark + ? AppColors.grey700.withValues(alpha: 0.3) + : AppColors.grey100.withValues(alpha: 0.3), width: 1.14667, ), ), diff --git a/lib/src/core/design_system/components/atoms/toggles/glass_follow_button.dart b/lib/src/core/design_system/components/atoms/toggles/glass_follow_button.dart index 9d071287..0116356b 100644 --- a/lib/src/core/design_system/components/atoms/toggles/glass_follow_button.dart +++ b/lib/src/core/design_system/components/atoms/toggles/glass_follow_button.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class GlassFollowButton extends StatelessWidget { const GlassFollowButton({ @@ -45,7 +45,10 @@ class GlassFollowButton extends StatelessWidget { ), ), child: Center( - child: Text(isFollowing ? unfollowText : followText, style: AppTypography.textExtraSmallMedium), + child: Text( + isFollowing ? unfollowText : followText, + style: AppTypography.textExtraSmallMedium, + ), ), ), ), diff --git a/lib/src/core/design_system/components/atoms/toggles/toggle.dart b/lib/src/core/design_system/components/atoms/toggles/toggle.dart index e0ac00cf..5bafd961 100644 --- a/lib/src/core/design_system/components/atoms/toggles/toggle.dart +++ b/lib/src/core/design_system/components/atoms/toggles/toggle.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; class Toggle extends StatelessWidget { const Toggle({ diff --git a/lib/src/core/design_system/components/atoms/video_progress_bar.dart b/lib/src/core/design_system/components/atoms/video_progress_bar.dart index 4479a456..0f8d8b3f 100644 --- a/lib/src/core/design_system/components/atoms/video_progress_bar.dart +++ b/lib/src/core/design_system/components/atoms/video_progress_bar.dart @@ -38,7 +38,8 @@ class DSVideoProgressBar extends StatelessWidget { /// Dense variant (slightly smaller thumb and height adjustments if true). final bool dense; - /// Whether this widget should react to gestures. If true, wraps a GestureDetector. + /// Whether this widget should react to gestures. + /// If true, wraps a GestureDetector. final bool enableGestures; /// Height of the interactive tap/drag target when gestures are enabled. @@ -93,8 +94,12 @@ class DSVideoProgressBar extends StatelessWidget { decoration: BoxDecoration( color: Colors.pinkAccent.withValues(alpha: 0.13), border: Border( - top: BorderSide(color: Colors.pinkAccent.withValues(alpha: 0.3)), - bottom: BorderSide(color: Colors.pinkAccent.withValues(alpha: 0.6)), + top: BorderSide( + color: Colors.pinkAccent.withValues(alpha: 0.3), + ), + bottom: BorderSide( + color: Colors.pinkAccent.withValues(alpha: 0.6), + ), ), ), ), @@ -191,7 +196,12 @@ class _ProgressPainter extends CustomPainter { final bufferedPaint = Paint()..color = bufferedColor; for (final (start, end) in bufferedSegments) { - final segRect = Rect.fromLTWH(size.width * start, 0, size.width * (end - start), size.height); + final segRect = Rect.fromLTWH( + size.width * start, + 0, + size.width * (end - start), + size.height, + ); canvas.save(); canvas.clipRect(trackRect); canvas.drawRect(segRect, bufferedPaint); @@ -261,7 +271,8 @@ class _GestureWrapperState extends State<_GestureWrapper> { return GestureDetector( behavior: HitTestBehavior.translucent, onHorizontalDragStart: (_) => widget.onDragStart?.call(), - onHorizontalDragUpdate: (d) => _handleUpdate(context, d.localPosition), + onHorizontalDragUpdate: (d) => + _handleUpdate(context, d.localPosition), onHorizontalDragEnd: (_) => widget.onDragEnd?.call(_lastFraction), onTapDown: (d) { widget.onDragStart?.call(); diff --git a/lib/src/core/design_system/components/molecules/app_tab_bar.dart b/lib/src/core/design_system/components/molecules/app_tab_bar.dart index 7a00fc1c..b6b24747 100644 --- a/lib/src/core/design_system/components/molecules/app_tab_bar.dart +++ b/lib/src/core/design_system/components/molecules/app_tab_bar.dart @@ -22,10 +22,18 @@ class AppTabBar extends StatelessWidget { decoration: BoxDecoration( color: backgroundColor ?? theme.scaffoldBackgroundColor, border: showBottomDivider - ? Border(bottom: BorderSide(color: bottomDividerColor ?? theme.dividerColor, width: 0.5)) + ? Border( + bottom: BorderSide( + color: bottomDividerColor ?? theme.dividerColor, + width: 0.5, + ), + ) : null, ), - child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: tabs), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: tabs, + ), ); } } diff --git a/lib/src/core/design_system/components/molecules/create_media_sheet.dart b/lib/src/core/design_system/components/molecules/create_media_sheet.dart index b0002479..4095fe0d 100644 --- a/lib/src/core/design_system/components/molecules/create_media_sheet.dart +++ b/lib/src/core/design_system/components/molecules/create_media_sheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -/// Shows a standardized bottom sheet for creating media: Record, Upload Video, Upload Images. +/// Shows a standardized bottom sheet for creating media: +/// Record, Upload Video, Upload Images. /// Only renders the actions whose callbacks are provided (non-null). Future showCreateMediaSheet( BuildContext context, { @@ -28,7 +29,10 @@ Future showCreateMediaSheet( if (onRecord != null) ListTile( leading: Icon(Icons.camera_alt, color: colorScheme.onSurface), - title: Text('Record', style: TextStyle(color: colorScheme.onSurface)), + title: Text( + 'Record', + style: TextStyle(color: colorScheme.onSurface), + ), onTap: () { Navigator.of(context).pop(); onRecord(); @@ -37,7 +41,10 @@ Future showCreateMediaSheet( if (onUploadVideo != null) ListTile( leading: Icon(Icons.videocam, color: colorScheme.onSurface), - title: Text('Upload Video', style: TextStyle(color: colorScheme.onSurface)), + title: Text( + 'Upload Video', + style: TextStyle(color: colorScheme.onSurface), + ), onTap: () { Navigator.of(context).pop(); onUploadVideo(); @@ -45,8 +52,14 @@ Future showCreateMediaSheet( ), if (onUploadImages != null) ListTile( - leading: Icon(Icons.photo_library, color: colorScheme.onSurface), - title: Text('Upload Images', style: TextStyle(color: colorScheme.onSurface)), + leading: Icon( + Icons.photo_library, + color: colorScheme.onSurface, + ), + title: Text( + 'Upload Images', + style: TextStyle(color: colorScheme.onSurface), + ), onTap: () { Navigator.of(context).pop(); onUploadImages(); diff --git a/lib/src/core/design_system/components/molecules/feed_card.dart b/lib/src/core/design_system/components/molecules/feed_card.dart index bd582f96..e33df9e9 100644 --- a/lib/src/core/design_system/components/molecules/feed_card.dart +++ b/lib/src/core/design_system/components/molecules/feed_card.dart @@ -1,12 +1,12 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/shapes.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/feed_models.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/shapes.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; class FeedCard extends StatelessWidget { const FeedCard({ @@ -134,7 +134,11 @@ class FeedCard extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(_title, style: AppTypography.textSmallBold, overflow: TextOverflow.ellipsis), + Text( + _title, + style: AppTypography.textSmallBold, + overflow: TextOverflow.ellipsis, + ), if (_subtitle != null) Text( _subtitle!, @@ -194,14 +198,20 @@ class FeedCard extends StatelessWidget { : AppColors.lightGreyButton; final textStyle = AppTypography.textSmallMedium.copyWith( - color: isPrimary ? Colors.white : (isDark ? AppColors.grey100 : AppColors.grey900), + color: isPrimary + ? Colors.white + : (isDark ? AppColors.grey100 : AppColors.grey900), ); return InteractivePressable( onTap: _actionCallback, borderRadius: const BorderRadius.all(Radius.circular(8)), child: Container( - constraints: const BoxConstraints(minWidth: 110, minHeight: 36, maxHeight: 36), + constraints: const BoxConstraints( + minWidth: 110, + minHeight: 36, + maxHeight: 36, + ), padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: backgroundColor, @@ -209,12 +219,18 @@ class FeedCard extends StatelessWidget { border: isPrimary ? null : Border.all( - color: isDark ? Colors.white.withValues(alpha: 0.15) : AppColors.grey200, + color: isDark + ? Colors.white.withValues(alpha: 0.15) + : AppColors.grey200, width: 1.1, ), ), alignment: Alignment.center, - child: Text(_actionLabel, style: textStyle, overflow: TextOverflow.ellipsis), + child: Text( + _actionLabel, + style: textStyle, + overflow: TextOverflow.ellipsis, + ), ), ); } @@ -249,7 +265,8 @@ class _FeedAvatar extends StatelessWidget { width: 36, height: 36, fit: BoxFit.cover, - placeholder: (context, url) => _FallbackAvatar(isDark: isDark, showLoader: true), + placeholder: (context, url) => + _FallbackAvatar(isDark: isDark, showLoader: true), errorWidget: (context, url, error) => _FallbackAvatar(isDark: isDark), ), ); @@ -281,7 +298,9 @@ class _FallbackAvatar extends StatelessWidget { height: 14, child: CircularProgressIndicator( strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(AppColors.primary500), + valueColor: AlwaysStoppedAnimation( + AppColors.primary500, + ), ), ), ) diff --git a/lib/src/core/design_system/components/molecules/feed_tag_list.dart b/lib/src/core/design_system/components/molecules/feed_tag_list.dart index 89ea90a2..1b27d44d 100644 --- a/lib/src/core/design_system/components/molecules/feed_tag_list.dart +++ b/lib/src/core/design_system/components/molecules/feed_tag_list.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/tags/feed_tag.dart'; +import 'package:spark/src/core/design_system/components/atoms/tags/feed_tag.dart'; class FeedTagList extends StatefulWidget { const FeedTagList({ diff --git a/lib/src/core/design_system/components/molecules/glass_avatar.dart b/lib/src/core/design_system/components/molecules/glass_avatar.dart index 67d57dcc..9adfc7a6 100644 --- a/lib/src/core/design_system/components/molecules/glass_avatar.dart +++ b/lib/src/core/design_system/components/molecules/glass_avatar.dart @@ -1,9 +1,9 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/ui/widgets/user_avatar.dart'; +import 'package:spark/src/core/ui/widgets/user_avatar.dart'; -/// Circular avatar with a subtle glass border similar to selected FeedTag style. +/// Circular avatar with subtle glass border similar to selected FeedTag style. class GlassAvatar extends StatelessWidget { const GlassAvatar({ required this.imageUrl, diff --git a/lib/src/core/design_system/components/molecules/glass_input.dart b/lib/src/core/design_system/components/molecules/glass_input.dart index 4b0fe13f..5468a4d4 100644 --- a/lib/src/core/design_system/components/molecules/glass_input.dart +++ b/lib/src/core/design_system/components/molecules/glass_input.dart @@ -1,8 +1,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class GlassInput extends StatelessWidget { final TextEditingController? controller; @@ -80,10 +80,14 @@ class GlassInput extends StatelessWidget { child: Container( height: 38, decoration: BoxDecoration( - color: isDark ? Colors.white.withAlpha(13) : Colors.black.withAlpha(13), + color: isDark + ? Colors.white.withAlpha(13) + : Colors.black.withAlpha(13), borderRadius: BorderRadius.circular(borderRadius), border: Border.all( - color: isDark ? Colors.white.withAlpha(37) : Colors.black.withAlpha(37), + color: isDark + ? Colors.white.withAlpha(37) + : Colors.black.withAlpha(37), ), ), child: Padding( @@ -92,7 +96,8 @@ class GlassInput extends StatelessWidget { children: [ if (leadingWidgets != null) ...leadingWidgets!, - if (leadingWidgets != null && leadingWidgets!.isNotEmpty) const SizedBox(width: 8), + if (leadingWidgets != null && leadingWidgets!.isNotEmpty) + const SizedBox(width: 8), Expanded( child: TextField( @@ -107,12 +112,16 @@ class GlassInput extends StatelessWidget { fillColor: Colors.transparent, hoverColor: Colors.transparent, isDense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), + contentPadding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 4, + ), ), ), ), - if (actionWidgets != null && actionWidgets!.isNotEmpty) const SizedBox(width: 8), + if (actionWidgets != null && actionWidgets!.isNotEmpty) + const SizedBox(width: 8), if (actionWidgets != null) ...actionWidgets!, @@ -121,9 +130,15 @@ class GlassInput extends StatelessWidget { IconButton( visualDensity: VisualDensity.compact, padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: 32, minHeight: 32), + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), onPressed: onSendMessage, - icon: AppIcons.send(size: 18, color: isDark ? Colors.white : Colors.black), + icon: AppIcons.send( + size: 18, + color: isDark ? Colors.white : Colors.black, + ), ), ], ], diff --git a/lib/src/core/design_system/components/molecules/known_interactions_bar.dart b/lib/src/core/design_system/components/molecules/known_interactions_bar.dart index bce23bcc..4cee4315 100644 --- a/lib/src/core/design_system/components/molecules/known_interactions_bar.dart +++ b/lib/src/core/design_system/components/molecules/known_interactions_bar.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/avatar_stack.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/feed_models.dart'; -import 'package:sparksocial/src/core/ui/theme/theme.dart'; +import 'package:spark/src/core/design_system/components/atoms/avatar_stack.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; +import 'package:spark/src/core/ui/theme/theme.dart'; /// A widget that displays known interactions (reposts and likes) as /// overlapping avatar stacks with icons in frosted glass pill containers. diff --git a/lib/src/core/design_system/components/molecules/post_tile.dart b/lib/src/core/design_system/components/molecules/post_tile.dart index a53a5593..2bc915fe 100644 --- a/lib/src/core/design_system/components/molecules/post_tile.dart +++ b/lib/src/core/design_system/components/molecules/post_tile.dart @@ -3,10 +3,10 @@ import 'dart:ui'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/shapes.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/shapes.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class PostTile extends StatelessWidget { final String thumbnailUrl; @@ -32,12 +32,17 @@ class PostTile extends StatelessWidget { @override Widget build(BuildContext context) { // Squircle shape from design tokens - final BorderRadiusGeometry radius = BorderRadius.circular(AppShapes.squircleRadius); + final BorderRadiusGeometry radius = BorderRadius.circular( + AppShapes.squircleRadius, + ); final side = BorderSide( width: AppShapes.squircleBorderWidth, color: Colors.white.withAlpha(AppShapes.squircleBorderAlpha), ); - final ShapeBorder shape = RoundedSuperellipseBorder(side: side, borderRadius: radius); + final ShapeBorder shape = RoundedSuperellipseBorder( + side: side, + borderRadius: radius, + ); return GestureDetector( onTap: onTap, @@ -93,9 +98,15 @@ class PostTile extends StatelessWidget { bottom: 0, right: 0, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 6, + ), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 4, + ), child: seen ? Text( 'seen', @@ -109,10 +120,11 @@ class PostTile extends StatelessWidget { children: [ Text( _formatViews(likes), - style: AppTypography.textExtraSmallMedium.copyWith( - color: AppColors.greyWhite, - fontSize: 13, - ), + style: AppTypography.textExtraSmallMedium + .copyWith( + color: AppColors.greyWhite, + fontSize: 13, + ), ), const SizedBox(width: 4), AppIcons.likeMini( diff --git a/lib/src/core/design_system/components/molecules/profile_action_buttons.dart b/lib/src/core/design_system/components/molecules/profile_action_buttons.dart index b503112a..c3b98676 100644 --- a/lib/src/core/design_system/components/molecules/profile_action_buttons.dart +++ b/lib/src/core/design_system/components/molecules/profile_action_buttons.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/text_button.dart' as ds; -import 'package:sparksocial/src/core/design_system/components/atoms/toggles/follow_button.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/text_button.dart' + as ds; +import 'package:spark/src/core/design_system/components/atoms/toggles/follow_button.dart'; class ProfileActionButtons extends StatelessWidget { const ProfileActionButtons({ diff --git a/lib/src/core/design_system/components/molecules/profile_avatar.dart b/lib/src/core/design_system/components/molecules/profile_avatar.dart index abf84e27..e0d35a20 100644 --- a/lib/src/core/design_system/components/molecules/profile_avatar.dart +++ b/lib/src/core/design_system/components/molecules/profile_avatar.dart @@ -1,8 +1,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; -import 'package:sparksocial/src/core/ui/foundation/colors.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/ui/foundation/colors.dart'; class ProfileAvatar extends StatelessWidget { const ProfileAvatar({ @@ -38,7 +38,8 @@ class ProfileAvatar extends StatelessWidget { height: size, fit: BoxFit.cover, placeholder: (context, url) => _buildPlaceholder(context, isDarkMode), - errorWidget: (context, url, error) => _buildPlaceholder(context, isDarkMode), + errorWidget: (context, url, error) => + _buildPlaceholder(context, isDarkMode), ), ); } else { @@ -58,10 +59,14 @@ class ProfileAvatar extends StatelessWidget { gradient: AppGradients.accent, ) : BoxDecoration( - color: isDarkMode ? AppColors.darkPurple : AppColors.lightLavender, + color: isDarkMode + ? AppColors.darkPurple + : AppColors.lightLavender, shape: BoxShape.circle, border: Border.all( - color: isDarkMode ? AppColors.darkPurple : AppColors.lightLavender, + color: isDarkMode + ? AppColors.darkPurple + : AppColors.lightLavender, width: 2, ), ), diff --git a/lib/src/core/design_system/components/molecules/profile_card.dart b/lib/src/core/design_system/components/molecules/profile_card.dart index 71c0084b..c3c43c9f 100644 --- a/lib/src/core/design_system/components/molecules/profile_card.dart +++ b/lib/src/core/design_system/components/molecules/profile_card.dart @@ -1,9 +1,9 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/toggles/follow_button.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/shapes.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/toggles/follow_button.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/shapes.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class ProfileCard extends StatelessWidget { const ProfileCard({ @@ -102,22 +102,30 @@ class ProfileCard extends StatelessWidget { errorWidget: (context, url, error) => Container( width: 36, height: 36, - color: isDark ? AppColors.grey600 : AppColors.grey300, + color: isDark + ? AppColors.grey600 + : AppColors.grey300, child: Icon( Icons.person, size: 20, - color: isDark ? AppColors.grey400 : AppColors.grey600, + color: isDark + ? AppColors.grey400 + : AppColors.grey600, ), ), ) : Container( width: 36, height: 36, - color: isDark ? AppColors.grey600 : AppColors.grey300, + color: isDark + ? AppColors.grey600 + : AppColors.grey300, child: Icon( Icons.person, size: 20, - color: isDark ? AppColors.grey400 : AppColors.grey600, + color: isDark + ? AppColors.grey400 + : AppColors.grey600, ), ), ), @@ -128,7 +136,10 @@ class ProfileCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(userName, style: AppTypography.textSmallBold), - Text(userHandle, style: AppTypography.textSmallThin), + Text( + userHandle, + style: AppTypography.textSmallThin, + ), if (description?.isNotEmpty ?? false) ...[ const SizedBox(height: 3), Text( diff --git a/lib/src/core/design_system/components/molecules/profile_info.dart b/lib/src/core/design_system/components/molecules/profile_info.dart index f27ccc53..e168b2ab 100644 --- a/lib/src/core/design_system/components/molecules/profile_info.dart +++ b/lib/src/core/design_system/components/molecules/profile_info.dart @@ -1,7 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; -import 'package:sparksocial/src/core/ui/foundation/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/ui/foundation/colors.dart'; class ProfileInfo extends StatelessWidget { const ProfileInfo({ @@ -34,7 +34,9 @@ class ProfileInfo extends StatelessWidget { const SizedBox(height: 8), _ProfileDescriptionText( text: description!, - style: AppTypography.textSmallBold.copyWith(color: theme.colorScheme.onSurface), + style: AppTypography.textSmallBold.copyWith( + color: theme.colorScheme.onSurface, + ), onMentionTap: onMentionTap, ), ], @@ -74,7 +76,9 @@ class _ProfileDescriptionText extends StatelessWidget { for (final match in usernameMatches) { if (match.start > lastEnd) { - spans.add(TextSpan(text: text.substring(lastEnd, match.start), style: style)); + spans.add( + TextSpan(text: text.substring(lastEnd, match.start), style: style), + ); } final username = match.group(0)!; @@ -129,8 +133,6 @@ class _ProfileLinkItem extends StatelessWidget { padding: const EdgeInsets.only(top: 4, bottom: 2), child: Row( children: [ - // const Icon(FluentIcons.link_24_regular, size: 16, color: linkColor), - // const SizedBox(width: 6), Expanded( child: Text( url, diff --git a/lib/src/core/design_system/components/molecules/profile_stats.dart b/lib/src/core/design_system/components/molecules/profile_stats.dart index d09020f5..c7421310 100644 --- a/lib/src/core/design_system/components/molecules/profile_stats.dart +++ b/lib/src/core/design_system/components/molecules/profile_stats.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class ProfileStats extends StatelessWidget { const ProfileStats({ @@ -58,7 +58,8 @@ class _StatItem extends StatelessWidget { Text( count, style: AppTypography.textLargeBold.copyWith( - color: theme.textTheme.bodyLarge?.color ?? theme.colorScheme.onSurface, + color: + theme.textTheme.bodyLarge?.color ?? theme.colorScheme.onSurface, ), ), const SizedBox(height: 4), @@ -66,7 +67,9 @@ class _StatItem extends StatelessWidget { child: Text( label, style: AppTypography.textSmallThin.copyWith( - color: theme.textTheme.bodyMedium?.color ?? theme.colorScheme.onSurfaceVariant, + color: + theme.textTheme.bodyMedium?.color ?? + theme.colorScheme.onSurfaceVariant, fontSize: 14, ), ), diff --git a/lib/src/core/design_system/components/molecules/profile_tab_bar.dart b/lib/src/core/design_system/components/molecules/profile_tab_bar.dart index 732fc90c..66b70b3d 100644 --- a/lib/src/core/design_system/components/molecules/profile_tab_bar.dart +++ b/lib/src/core/design_system/components/molecules/profile_tab_bar.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/profile_tab_item.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/app_tab_bar.dart'; +import 'package:spark/src/core/design_system/components/atoms/profile_tab_item.dart'; +import 'package:spark/src/core/design_system/components/molecules/app_tab_bar.dart'; class ProfileTabBar extends StatelessWidget { const ProfileTabBar({ diff --git a/lib/src/core/design_system/components/molecules/recording_button.dart b/lib/src/core/design_system/components/molecules/recording_button.dart index 00ebd13d..90280a21 100644 --- a/lib/src/core/design_system/components/molecules/recording_button.dart +++ b/lib/src/core/design_system/components/molecules/recording_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; class RecordingButton extends StatefulWidget { const RecordingButton({ @@ -17,7 +17,8 @@ class RecordingButton extends StatefulWidget { State createState() => _RecordingButtonState(); } -class _RecordingButtonState extends State with SingleTickerProviderStateMixin { +class _RecordingButtonState extends State + with SingleTickerProviderStateMixin { late AnimationController _pulseController; @override @@ -78,12 +79,15 @@ class _RecordingButtonState extends State with SingleTickerProv margin: EdgeInsets.all(widget.isRecording ? 8 : 4), decoration: BoxDecoration( shape: BoxShape.circle, - color: widget.isRecording ? AppColors.red500 : AppColors.greyWhite, + color: widget.isRecording + ? AppColors.red500 + : AppColors.greyWhite, boxShadow: widget.isRecording ? [ BoxShadow( color: AppColors.red500.withAlpha( - (128 * (0.5 + 0.5 * _pulseController.value)).toInt(), + (128 * (0.5 + 0.5 * _pulseController.value)) + .toInt(), ), blurRadius: 20, spreadRadius: 4, diff --git a/lib/src/core/design_system/components/molecules/recording_timer.dart b/lib/src/core/design_system/components/molecules/recording_timer.dart index 34b5a93e..aa00ba75 100644 --- a/lib/src/core/design_system/components/molecules/recording_timer.dart +++ b/lib/src/core/design_system/components/molecules/recording_timer.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class RecordingTimer extends StatelessWidget { const RecordingTimer({ @@ -17,7 +17,9 @@ class RecordingTimer extends StatelessWidget { final progress = duration.inMilliseconds / maxDuration.inMilliseconds; final minutes = duration.inMinutes; final seconds = duration.inSeconds % 60; - final formattedTime = '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}'; + final formattedTime = + '${minutes.toString().padLeft(1, '0')}:' + '${seconds.toString().padLeft(2, '0')}'; final isWarning = progress >= 0.9; final textColor = isWarning ? AppColors.red500 : AppColors.greyWhite; @@ -28,7 +30,9 @@ class RecordingTimer extends StatelessWidget { color: Colors.black.withAlpha(128), borderRadius: BorderRadius.circular(20), border: Border.all( - color: isWarning ? AppColors.red500.withAlpha(180) : Colors.white.withAlpha(50), + color: isWarning + ? AppColors.red500.withAlpha(180) + : Colors.white.withAlpha(50), width: 1.5, ), ), diff --git a/lib/src/core/design_system/components/molecules/settings_feed_card.dart b/lib/src/core/design_system/components/molecules/settings_feed_card.dart index b339e361..493fce50 100644 --- a/lib/src/core/design_system/components/molecules/settings_feed_card.dart +++ b/lib/src/core/design_system/components/molecules/settings_feed_card.dart @@ -1,11 +1,11 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/shapes.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/feed_models.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/shapes.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; enum SettingsFeedCardMode { display, @@ -81,7 +81,11 @@ class SettingsFeedCard extends StatelessWidget { return _buildDisplayMode(context, radius, borderColor); } - Widget _buildDisplayMode(BuildContext context, BorderRadius radius, Color borderColor) { + Widget _buildDisplayMode( + BuildContext context, + BorderRadius radius, + Color borderColor, + ) { final isDark = Theme.of(context).brightness == Brightness.dark; final backgroundColor = isDark ? AppColors.grey700 : AppColors.grey100; @@ -110,7 +114,11 @@ class SettingsFeedCard extends StatelessWidget { ); } - Widget _buildEditMode(BuildContext context, BorderRadius radius, Color borderColor) { + Widget _buildEditMode( + BuildContext context, + BorderRadius radius, + Color borderColor, + ) { final isDark = Theme.of(context).brightness == Brightness.dark; final backgroundColor = isDark ? AppColors.grey700 : AppColors.grey100; @@ -156,7 +164,8 @@ class SettingsFeedCard extends StatelessWidget { width: 40, height: 40, fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) => _buildFallbackAvatar(), + errorBuilder: (context, error, stackTrace) => + _buildFallbackAvatar(), ) : _buildFallbackAvatar(), ), @@ -172,7 +181,9 @@ class SettingsFeedCard extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), child: Icon( - _isTimeline ? FluentIcons.people_24_regular : FluentIcons.feed_24_regular, + _isTimeline + ? FluentIcons.people_24_regular + : FluentIcons.feed_24_regular, size: 20, color: Colors.white, ), @@ -215,7 +226,8 @@ class SettingsFeedCard extends StatelessWidget { } Widget _buildLikeButton() { - if (_isTimeline) return const SizedBox.shrink(); // Don't show like button for timeline + // Don't show like button for timeline + if (_isTimeline) return const SizedBox.shrink(); return InteractivePressable( onTap: _isLiked ? onUnlike : onLike, @@ -226,7 +238,9 @@ class SettingsFeedCard extends StatelessWidget { color: _isLiked ? AppColors.primary600 : Colors.transparent, borderRadius: BorderRadius.circular(6), border: Border.all( - color: _isLiked ? AppColors.primary600 : (Colors.grey.withAlpha(100)), + color: _isLiked + ? AppColors.primary600 + : (Colors.grey.withAlpha(100)), ), ), child: _isLiked diff --git a/lib/src/core/design_system/components/molecules/story_circle.dart b/lib/src/core/design_system/components/molecules/story_circle.dart index 40881bd8..3ff0da59 100644 --- a/lib/src/core/design_system/components/molecules/story_circle.dart +++ b/lib/src/core/design_system/components/molecules/story_circle.dart @@ -1,8 +1,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/gradients.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/gradients.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; enum StoryType { story, live, cf, create } @@ -208,7 +208,10 @@ class _CreateButton extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, gradient: AppGradients.accent, - border: Border.all(width: 2, color: Theme.of(context).colorScheme.surface), + border: Border.all( + width: 2, + color: Theme.of(context).colorScheme.surface, + ), ), child: AppIcons.add(size: 16, color: Colors.white), ), diff --git a/lib/src/core/design_system/components/organisms/bottom_nav_bar.dart b/lib/src/core/design_system/components/organisms/bottom_nav_bar.dart index f1f69101..46ab393a 100644 --- a/lib/src/core/design_system/components/organisms/bottom_nav_bar.dart +++ b/lib/src/core/design_system/components/organisms/bottom_nav_bar.dart @@ -1,8 +1,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/tokens/constants.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/tokens/constants.dart'; class SparkBottomNavBar extends StatelessWidget { const SparkBottomNavBar({ @@ -21,7 +21,8 @@ class SparkBottomNavBar extends StatelessWidget { Widget build(BuildContext context) { final bottomPadding = MediaQuery.of(context).padding.bottom; // Always use dark mode when on home tab (index 0) - final isDark = currentIndex == 0 || Theme.of(context).brightness == Brightness.dark; + final isDark = + currentIndex == 0 || Theme.of(context).brightness == Brightness.dark; return Column( mainAxisSize: MainAxisSize.min, @@ -32,7 +33,9 @@ class SparkBottomNavBar extends StatelessWidget { top: 12, bottom: 12 + bottomPadding, ), - color: isDark ? const Color.fromARGB(51, 0, 0, 0) : const Color.fromARGB(178, 255, 255, 255), + color: isDark + ? const Color.fromARGB(51, 0, 0, 0) + : const Color.fromARGB(178, 255, 255, 255), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -49,7 +52,9 @@ class SparkBottomNavBar extends StatelessWidget { isSelected: currentIndex == 1, onTap: () => onTap(1), builder: (c, selected) => selected - ? AppIcons.exploreFilled(color: isDark ? null : Colors.black) + ? AppIcons.exploreFilled( + color: isDark ? null : Colors.black, + ) : AppIcons.explore(color: isDark ? null : Colors.black), ), @@ -57,15 +62,21 @@ class SparkBottomNavBar extends StatelessWidget { isSelected: currentIndex == 2, onTap: () => onTap(2), builder: (c, selected) => selected - ? AppIcons.navbarPostFilled(color: isDark ? null : Colors.black) - : AppIcons.navbarPost(color: isDark ? null : Colors.black), + ? AppIcons.navbarPostFilled( + color: isDark ? null : Colors.black, + ) + : AppIcons.navbarPost( + color: isDark ? null : Colors.black, + ), ), _NavIcon( isSelected: currentIndex == 3, onTap: () => onTap(3), builder: (c, selected) => selected - ? AppIcons.messagesFilled(color: isDark ? null : Colors.black) + ? AppIcons.messagesFilled( + color: isDark ? null : Colors.black, + ) : AppIcons.messages(color: isDark ? null : Colors.black), ), @@ -92,11 +103,17 @@ class _BarBackground extends StatelessWidget { return ClipRRect( borderRadius: BorderRadius.circular(0), child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: AppConstants.blurBottomBar.toDouble(), sigmaY: AppConstants.blurBottomBar.toDouble()), + filter: ImageFilter.blur( + sigmaX: AppConstants.blurBottomBar.toDouble(), + sigmaY: AppConstants.blurBottomBar.toDouble(), + ), child: DecoratedBox( decoration: BoxDecoration( border: Border( - top: BorderSide(color: Colors.white.withValues(alpha: 0.08), width: 2), + top: BorderSide( + color: Colors.white.withValues(alpha: 0.08), + width: 2, + ), ), ), child: child, @@ -146,7 +163,8 @@ class _ProfileAvatar extends StatelessWidget { @override Widget build(BuildContext context) { // Always use dark mode when on home tab (index 0) - final isDark = currentIndex == 0 || Theme.of(context).brightness == Brightness.dark; + final isDark = + currentIndex == 0 || Theme.of(context).brightness == Brightness.dark; final avatar = Container( width: 34, height: 34, @@ -155,8 +173,12 @@ class _ProfileAvatar extends StatelessWidget { border: Border.all( color: Colors.white, ), - image: image is AssetImage ? null : DecorationImage(image: image, fit: BoxFit.cover), - color: image is AssetImage ? (isDark ? const Color(0xFF424242) : const Color(0xFFE0E0E0)) : null, + image: image is AssetImage + ? null + : DecorationImage(image: image, fit: BoxFit.cover), + color: image is AssetImage + ? (isDark ? const Color(0xFF424242) : const Color(0xFFE0E0E0)) + : null, ), child: image is AssetImage ? Icon( diff --git a/lib/src/core/design_system/components/organisms/side_action_bar.dart b/lib/src/core/design_system/components/organisms/side_action_bar.dart index 8f8ad18f..7e18aa6f 100644 --- a/lib/src/core/design_system/components/organisms/side_action_bar.dart +++ b/lib/src/core/design_system/components/organisms/side_action_bar.dart @@ -1,8 +1,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/ui/foundation/colors.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/ui/foundation/colors.dart'; /// Curate popover item data class CurateDestination { @@ -40,7 +40,8 @@ class SparkSideActionBar extends StatefulWidget { final VoidCallback? onLike; final VoidCallback? onComment; final VoidCallback? onRepost; - final VoidCallback? onCurate; // called after a feed selection (or when opening?) + final VoidCallback? + onCurate; // called after a feed selection (or when opening?) final VoidCallback? onShare; final VoidCallback? onSoundTap; final VoidCallback? onOptions; @@ -83,13 +84,18 @@ class _SparkSideActionBarState extends State { void _showPopover() { if (!mounted) return; - final renderBox = _curateKey.currentContext?.findRenderObject() as RenderBox?; - final overlayBox = Overlay.of(context).context.findRenderObject() as RenderBox?; + final renderBox = + _curateKey.currentContext?.findRenderObject() as RenderBox?; + final overlayBox = + Overlay.of(context).context.findRenderObject() as RenderBox?; if (renderBox == null || overlayBox == null) return; final target = renderBox.localToGlobal(Offset.zero, ancestor: overlayBox); final size = renderBox.size; - final iconCenter = Offset(target.dx + size.width / 2, target.dy + size.height / 2); + final iconCenter = Offset( + target.dx + size.width / 2, + target.dy + size.height / 2, + ); _overlay = OverlayEntry( builder: (ctx) { @@ -99,7 +105,6 @@ class _SparkSideActionBarState extends State { child: Stack( children: [ Positioned( - // Place so the connector circle (shiftX=30) centers behind icon; fine-tune offsets empirically. top: iconCenter.dy - 48, right: MediaQuery.of(context).size.width - target.dx + 16 - 30, child: _CuratePopover( @@ -116,7 +121,7 @@ class _SparkSideActionBarState extends State { ); }, ); - // Add icon overlay above popover for correct z-order (bar bg -> popover -> actual icon) + // Icon overlay above popover for z-order (bar bg -> popover -> actual icon) _overlayIcon = OverlayEntry( builder: (ctx) => Positioned( left: target.dx, @@ -149,7 +154,9 @@ class _SparkSideActionBarState extends State { _ActionItem( isActive: widget.isLiked, label: widget.likeCount, - icon: widget.isLiked ? AppIcons.likeFilled(size: 32) : AppIcons.like(size: 32), + icon: widget.isLiked + ? AppIcons.likeFilled(size: 32) + : AppIcons.like(size: 32), onTap: widget.onLike, ), const SizedBox(height: 13), @@ -161,7 +168,10 @@ class _SparkSideActionBarState extends State { const SizedBox(height: 13), _ActionItem( isActive: widget.isReposted, - icon: AppIcons.repost(size: 32, color: widget.isReposted ? AppColors.green : null), + icon: AppIcons.repost( + size: 32, + color: widget.isReposted ? AppColors.green : null, + ), label: widget.repostCount, onTap: widget.onRepost, ), @@ -233,7 +243,8 @@ class _ActionItem extends StatefulWidget { State<_ActionItem> createState() => _ActionItemState(); } -class _ActionItemState extends State<_ActionItem> with SingleTickerProviderStateMixin { +class _ActionItemState extends State<_ActionItem> + with SingleTickerProviderStateMixin { late AnimationController _bounceController; late Animation _bounceAnimation; @@ -244,11 +255,14 @@ class _ActionItemState extends State<_ActionItem> with SingleTickerProviderState duration: const Duration(milliseconds: 300), vsync: this, ); - _bounceAnimation = TweenSequence([ - TweenSequenceItem(tween: Tween(begin: 1, end: 1.3), weight: 40), - TweenSequenceItem(tween: Tween(begin: 1.3, end: 0.9), weight: 30), - TweenSequenceItem(tween: Tween(begin: 0.9, end: 1.05), weight: 30), - ]).animate(CurvedAnimation(parent: _bounceController, curve: Curves.easeOut)); + _bounceAnimation = + TweenSequence([ + TweenSequenceItem(tween: Tween(begin: 1, end: 1.3), weight: 40), + TweenSequenceItem(tween: Tween(begin: 1.3, end: 0.9), weight: 30), + TweenSequenceItem(tween: Tween(begin: 0.9, end: 1.05), weight: 30), + ]).animate( + CurvedAnimation(parent: _bounceController, curve: Curves.easeOut), + ); } @override @@ -276,11 +290,17 @@ class _ActionItemState extends State<_ActionItem> with SingleTickerProviderState animation: _bounceAnimation, builder: (context, child) { return Transform.scale( - scale: _bounceController.isAnimating ? _bounceAnimation.value : (widget.isActive ? 1.05 : 1.0), + scale: _bounceController.isAnimating + ? _bounceAnimation.value + : (widget.isActive ? 1.05 : 1.0), child: child, ); }, - child: SizedBox(width: 40, height: 40, child: Center(child: widget.icon)), + child: SizedBox( + width: 40, + height: 40, + child: Center(child: widget.icon), + ), ), if (widget.label != null && widget.label!.isNotEmpty) Text( @@ -393,7 +413,6 @@ class _ConnectorPopover extends StatelessWidget { child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( - // Increased vertical padding + larger right padding to account for bigger connector circle padding: const EdgeInsets.fromLTRB(20, 18, 60, 18), decoration: const BoxDecoration(), child: child, @@ -429,14 +448,22 @@ class _ConnectorPopoverClipper extends CustomClipper { @override Path getClip(Size size) { // Main body excludes connector width minus overlap - final bodyWidth = size.width - (connectorRadius - connectorShiftX) + overlap; - final bodyRect = RRect.fromLTRBR(0, 0, bodyWidth, size.height, const Radius.circular(radius)); + final bodyWidth = + size.width - (connectorRadius - connectorShiftX) + overlap; + final bodyRect = RRect.fromLTRBR( + 0, + 0, + bodyWidth, + size.height, + const Radius.circular(radius), + ); final rectPath = Path()..addRRect(bodyRect); final circleCenter = Offset( bodyWidth - overlap + connectorShiftX, connectorRadius + connectorShiftDown, ); // shifted circle - final circlePath = Path()..addOval(Rect.fromCircle(center: circleCenter, radius: connectorRadius)); + final circlePath = Path() + ..addOval(Rect.fromCircle(center: circleCenter, radius: connectorRadius)); return Path.combine(PathOperation.union, rectPath, circlePath); } diff --git a/lib/src/core/design_system/templates/chat_list_page_template.dart b/lib/src/core/design_system/templates/chat_list_page_template.dart index 0219413e..86f62437 100644 --- a/lib/src/core/design_system/templates/chat_list_page_template.dart +++ b/lib/src/core/design_system/templates/chat_list_page_template.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/tab_item.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/app_tab_bar.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/glass_avatar.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/components/atoms/tab_item.dart'; +import 'package:spark/src/core/design_system/components/molecules/app_tab_bar.dart'; +import 'package:spark/src/core/design_system/components/molecules/glass_avatar.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class ChatListItemData { const ChatListItemData({ @@ -63,7 +63,12 @@ class ChatListPageTemplate extends StatelessWidget { onPressed: onAddTap, icon: AppIcons.addUser(color: theme.colorScheme.onSurface), ), - title: Text(title, style: AppTypography.textMediumBold.copyWith(color: theme.colorScheme.onSurface)), + title: Text( + title, + style: AppTypography.textMediumBold.copyWith( + color: theme.colorScheme.onSurface, + ), + ), ), body: Column( children: [ @@ -103,15 +108,29 @@ class _Tabs extends StatelessWidget { return AppTabBar( tabs: [ AppTabItem( - activeChild: const Text('Messages', style: AppTypography.textMediumBold), - inactiveChild: Text('Messages', style: AppTypography.textMediumBold.copyWith(color: inactive)), + activeChild: const Text( + 'Messages', + style: AppTypography.textMediumBold, + ), + inactiveChild: Text( + 'Messages', + style: AppTypography.textMediumBold.copyWith(color: inactive), + ), isSelected: selectedIndex == 0, onTap: () => onChanged(0), indicatorColor: theme.colorScheme.onSurface, ), // AppTabItem( - // activeChild: Text('Activity', style: AppTypography.textMediumBold.copyWith(color: theme.colorScheme.onSurface)), - // inactiveChild: Text('Activity', style: AppTypography.textMediumBold.copyWith(color: inactive)), + // activeChild: Text( + // 'Activity', + // style: AppTypography.textMediumBold.copyWith( + // color: theme.colorScheme.onSurface, + // ), + // ), + // inactiveChild: Text( + // 'Activity', + // style: AppTypography.textMediumBold.copyWith(color: inactive), + // ), // isSelected: selectedIndex == 1, // onTap: () => onChanged(1), // indicatorColor: theme.colorScheme.onSurface, @@ -135,29 +154,44 @@ class _ChatTile extends StatelessWidget { onTap: onTap, horizontalTitleGap: 12, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - leading: GlassAvatar(imageUrl: data.avatarUrl ?? '', username: data.handle), + leading: GlassAvatar( + imageUrl: data.avatarUrl ?? '', + username: data.handle, + ), title: Row( children: [ Expanded( - child: Text(data.displayName, maxLines: 1, overflow: TextOverflow.ellipsis, style: AppTypography.textLargeBold), + child: Text( + data.displayName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppTypography.textLargeBold, + ), ), const SizedBox(width: 6), if (data.unread) ...[ Container( width: 10, height: 10, - decoration: BoxDecoration(color: theme.colorScheme.onSurface, shape: BoxShape.circle), + decoration: BoxDecoration( + color: theme.colorScheme.onSurface, + shape: BoxShape.circle, + ), ), const SizedBox(width: 6), Text( data.timestamp, - style: AppTypography.textExtraSmallThin.copyWith(color: onSurface.withAlpha(160)), + style: AppTypography.textExtraSmallThin.copyWith( + color: onSurface.withAlpha(160), + ), ), ] else ...[ Text( data.timestamp, - style: AppTypography.textExtraSmallThin.copyWith(color: onSurface.withAlpha(160)), + style: AppTypography.textExtraSmallThin.copyWith( + color: onSurface.withAlpha(160), + ), ), ], ], @@ -167,8 +201,12 @@ class _ChatTile extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: data.unread - ? AppTypography.textSmallBold.copyWith(color: onSurface.withAlpha(190)) - : AppTypography.textSmallMedium.copyWith(color: onSurface.withAlpha(190)), + ? AppTypography.textSmallBold.copyWith( + color: onSurface.withAlpha(190), + ) + : AppTypography.textSmallMedium.copyWith( + color: onSurface.withAlpha(190), + ), ), ); } diff --git a/lib/src/core/design_system/templates/chat_thread_page_template.dart b/lib/src/core/design_system/templates/chat_thread_page_template.dart index 5c6a6f21..8bda6236 100644 --- a/lib/src/core/design_system/templates/chat_thread_page_template.dart +++ b/lib/src/core/design_system/templates/chat_thread_page_template.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/glass_input.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/profile_avatar.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; +import 'package:spark/src/core/design_system/components/molecules/glass_input.dart'; +import 'package:spark/src/core/design_system/components/molecules/profile_avatar.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class ChatThreadPageTemplate extends StatelessWidget { const ChatThreadPageTemplate({ @@ -34,16 +34,27 @@ class ChatThreadPageTemplate extends StatelessWidget { leading: const AppLeadingButton(), title: Row( children: [ - ProfileAvatar(avatarUrl: avatarUrl, displayName: displayName, size: 36), + ProfileAvatar( + avatarUrl: avatarUrl, + displayName: displayName, + size: 36, + ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(displayName, style: AppTypography.textMediumBold.copyWith(color: theme.colorScheme.onSurface)), + Text( + displayName, + style: AppTypography.textMediumBold.copyWith( + color: theme.colorScheme.onSurface, + ), + ), Text( '@$handle', - style: AppTypography.textExtraSmallThin.copyWith(color: theme.colorScheme.onSurface.withAlpha(170)), + style: AppTypography.textExtraSmallThin.copyWith( + color: theme.colorScheme.onSurface.withAlpha(170), + ), ), ], ), @@ -54,7 +65,11 @@ class ChatThreadPageTemplate extends StatelessWidget { ), body: Column( children: [ - Container(height: 0.5, width: double.infinity, color: theme.colorScheme.outline), + Container( + height: 0.5, + width: double.infinity, + color: theme.colorScheme.outline, + ), Expanded(child: messagesWidget), SafeArea( top: false, diff --git a/lib/src/core/design_system/templates/feeds_bar_template.dart b/lib/src/core/design_system/templates/feeds_bar_template.dart index d4903f56..d1dd8bb2 100644 --- a/lib/src/core/design_system/templates/feeds_bar_template.dart +++ b/lib/src/core/design_system/templates/feeds_bar_template.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/feed_tag_list.dart'; +import 'package:spark/src/core/design_system/components/molecules/feed_tag_list.dart'; /// Design-only template for the Feeds Bar. /// diff --git a/lib/src/core/design_system/templates/image_review_page_template.dart b/lib/src/core/design_system/templates/image_review_page_template.dart index 87950198..a42c4fa4 100644 --- a/lib/src/core/design_system/templates/image_review_page_template.dart +++ b/lib/src/core/design_system/templates/image_review_page_template.dart @@ -1,18 +1,18 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/long_button.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/toggles/toggle.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/glass_input.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/shapes.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/long_button.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/components/atoms/toggles/toggle.dart'; +import 'package:spark/src/core/design_system/components/molecules/glass_input.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/shapes.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; /// Design-only template for the Image Review flow. /// -/// The template composes page chrome and common sections using the design system. +/// The template composes page chrome & common sections with the design system. /// Content-heavy areas (image pager and description) are provided as slots. class ImageReviewPageTemplate extends StatelessWidget { const ImageReviewPageTemplate({ @@ -74,7 +74,10 @@ class ImageReviewPageTemplate extends StatelessWidget { appBar: AppBar( backgroundColor: backgroundColor ?? colorScheme.surface, elevation: 0, - leading: AppLeadingButton(color: theme.textTheme.titleLarge?.color, tooltip: 'Back'), + leading: AppLeadingButton( + color: theme.textTheme.titleLarge?.color, + tooltip: 'Back', + ), title: Text( title, ), @@ -103,7 +106,9 @@ class ImageReviewPageTemplate extends StatelessWidget { SizedBox( width: double.infinity, child: LongButton( - label: canAddMore ? 'Add More Images ($imagesCount/$maxImages)' : 'Image Limit Reached', + label: canAddMore + ? 'Add More Images ($imagesCount/$maxImages)' + : 'Image Limit Reached', onPressed: canAddMore ? onAddMore : null, ), ), @@ -139,7 +144,10 @@ class ImageReviewPageTemplate extends StatelessWidget { child: const SizedBox( width: 20, height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: AppColors.greyWhite), + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColors.greyWhite, + ), ), ) : LongButton(label: postLabel, onPressed: onPost), @@ -173,8 +181,14 @@ class _ImagePager extends StatelessWidget { Widget build(BuildContext context) { if (imagePaths.isEmpty) return const SizedBox.shrink(); final radius = BorderRadiusGeometry.circular(AppShapes.squircleRadius); - final side = BorderSide(width: AppShapes.squircleBorderWidth, color: Colors.white.withAlpha(AppShapes.squircleBorderAlpha)); - final ShapeBorder shape = RoundedSuperellipseBorder(side: side, borderRadius: radius); + final side = BorderSide( + width: AppShapes.squircleBorderWidth, + color: Colors.white.withAlpha(AppShapes.squircleBorderAlpha), + ); + final ShapeBorder shape = RoundedSuperellipseBorder( + side: side, + borderRadius: radius, + ); return AspectRatio( aspectRatio: 1, @@ -207,18 +221,32 @@ class _ImagePager extends StatelessWidget { bottom: 8, left: 8, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.black.withAlpha(150), borderRadius: BorderRadius.circular(6), - border: Border.all(color: Colors.white.withAlpha(38)), + border: Border.all( + color: Colors.white.withAlpha(38), + ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.edit, color: AppColors.greyWhite, size: 16), + const Icon( + Icons.edit, + color: AppColors.greyWhite, + size: 16, + ), const SizedBox(width: 6), - Text('Tap to edit', style: AppTypography.textSmallBold.copyWith(color: AppColors.greyWhite)), + Text( + 'Tap to edit', + style: AppTypography.textSmallBold.copyWith( + color: AppColors.greyWhite, + ), + ), ], ), ), @@ -233,15 +261,26 @@ class _ImagePager extends StatelessWidget { GestureDetector( onTap: () => onAltEdit(index), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.black.withAlpha(100), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.white.withAlpha(38)), + border: Border.all( + color: Colors.white.withAlpha(38), + ), ), child: Row( children: [ - Text('ALT', style: AppTypography.textSmallBold.copyWith(color: AppColors.greyWhite)), + Text( + 'ALT', + style: AppTypography.textSmallBold + .copyWith( + color: AppColors.greyWhite, + ), + ), ], ), ), @@ -255,9 +294,14 @@ class _ImagePager extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.black.withAlpha(100), - border: Border.all(color: Colors.white.withAlpha(38)), + border: Border.all( + color: Colors.white.withAlpha(38), + ), + ), + child: AppIcons.cancel( + size: 18, + color: AppColors.greyWhite, ), - child: AppIcons.cancel(size: 18, color: AppColors.greyWhite), ), ), ], @@ -272,7 +316,10 @@ class _ImagePager extends StatelessWidget { Positioned( bottom: 10, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.black.withAlpha(100), borderRadius: BorderRadius.circular(10), @@ -280,7 +327,9 @@ class _ImagePager extends StatelessWidget { ), child: Text( '${currentPage + 1} / ${imagePaths.length}', - style: AppTypography.textExtraSmallMedium.copyWith(color: AppColors.greyWhite), + style: AppTypography.textExtraSmallMedium.copyWith( + color: AppColors.greyWhite, + ), ), ), ), @@ -316,7 +365,9 @@ class _DescriptionSection extends StatelessWidget { alignment: Alignment.centerRight, child: Text( '$count/$maxChars', - style: AppTypography.textSmallMedium.copyWith(color: Colors.white.withAlpha(160)), + style: AppTypography.textSmallMedium.copyWith( + color: Colors.white.withAlpha(160), + ), ), ), ], @@ -346,10 +397,15 @@ class _CrossPostSection extends StatelessWidget { border: Border.all(color: Colors.white.withAlpha(24)), ), child: ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 2, + ), title: Text( 'Post to Bluesky', - style: AppTypography.textMediumBold.copyWith(color: AppColors.greyWhite), + style: AppTypography.textMediumBold.copyWith( + color: AppColors.greyWhite, + ), ), trailing: Toggle(value: value, onChanged: onChanged), onTap: () => onChanged(!value), @@ -368,12 +424,19 @@ class _CrossPostSection extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon(Icons.info_outline, color: AppColors.rajah500, size: 20), + const Icon( + Icons.info_outline, + color: AppColors.rajah500, + size: 20, + ), const SizedBox(width: 8), Expanded( child: Text( - 'Bluesky supports a maximum of 4 images. Your Bluesky post will link to the full Spark post instead.', - style: AppTypography.textSmallMedium.copyWith(color: AppColors.rajah500), + 'Bluesky supports a maximum of 4 images. ' + 'Your Bluesky post will link to the Spark post instead.', + style: AppTypography.textSmallMedium.copyWith( + color: AppColors.rajah500, + ), ), ), ], diff --git a/lib/src/core/design_system/templates/info_bar_template.dart b/lib/src/core/design_system/templates/info_bar_template.dart index a3dea8f6..f9a69f03 100644 --- a/lib/src/core/design_system/templates/info_bar_template.dart +++ b/lib/src/core/design_system/templates/info_bar_template.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/follow_pill_button.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/profile_avatar.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/models.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/follow_pill_button.dart'; +import 'package:spark/src/core/design_system/components/molecules/profile_avatar.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/network/atproto/data/models/models.dart'; class InfoBarTemplate extends StatefulWidget { const InfoBarTemplate({ @@ -62,7 +62,8 @@ class InfoBarTemplate extends StatefulWidget { State createState() => _InfoBarTemplateState(); } -class _InfoBarTemplateState extends State with SingleTickerProviderStateMixin { +class _InfoBarTemplateState extends State + with SingleTickerProviderStateMixin { bool _isDescriptionExpanded = false; void _toggleDescription() { @@ -77,7 +78,10 @@ class _InfoBarTemplateState extends State with SingleTickerProv if (parts.length == 1) { return GestureDetector( onTap: widget.onHandleTap, - child: Text('@$handle', style: AppTypography.textSmallThin.copyWith(color: color)), + child: Text( + '@$handle', + style: AppTypography.textSmallThin.copyWith(color: color), + ), ); } @@ -92,7 +96,9 @@ class _InfoBarTemplateState extends State with SingleTickerProv TextSpan(text: '@$first'), TextSpan( text: '.$rest', - style: AppTypography.textSmallThin.copyWith(color: color.withAlpha(128)), + style: AppTypography.textSmallThin.copyWith( + color: color.withAlpha(128), + ), ), ], ), @@ -134,7 +140,9 @@ class _InfoBarTemplateState extends State with SingleTickerProv onTap: widget.onTitleTap, child: Text( widget.displayName, - style: AppTypography.textMediumBold.copyWith(color: textColor), + style: AppTypography.textMediumBold.copyWith( + color: textColor, + ), overflow: TextOverflow.ellipsis, ), ), @@ -168,8 +176,12 @@ class _InfoBarTemplateState extends State with SingleTickerProv child: Text( widget.description!, style: AppTypography.textSmallMedium.copyWith(color: textColor), - maxLines: _isDescriptionExpanded ? null : widget.descriptionMaxLines, - overflow: _isDescriptionExpanded ? TextOverflow.visible : TextOverflow.ellipsis, + maxLines: _isDescriptionExpanded + ? null + : widget.descriptionMaxLines, + overflow: _isDescriptionExpanded + ? TextOverflow.visible + : TextOverflow.ellipsis, ), ), ), @@ -180,7 +192,8 @@ class _InfoBarTemplateState extends State with SingleTickerProv spacing: 6, runSpacing: 6, children: [ - for (final label in widget.informLabels) _InformChip(label: label), + for (final label in widget.informLabels) + _InformChip(label: label), ], ), ], @@ -196,7 +209,9 @@ class _AltPill extends StatelessWidget { Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Material( - color: (isDark ? AppColors.grey600 : AppColors.lightGreyButton).withAlpha(180), + color: (isDark ? AppColors.grey600 : AppColors.lightGreyButton).withAlpha( + 180, + ), borderRadius: BorderRadius.circular(8), child: InkWell( borderRadius: BorderRadius.circular(8), @@ -205,7 +220,9 @@ class _AltPill extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), child: Text( 'ALT', - style: AppTypography.textExtraSmallMedium.copyWith(color: AppColors.greyWhite), + style: AppTypography.textExtraSmallMedium.copyWith( + color: AppColors.greyWhite, + ), ), ), ), @@ -234,7 +251,10 @@ class _InformChip extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text(label, style: AppTypography.textExtraSmallMedium.copyWith(color: text)), + Text( + label, + style: AppTypography.textExtraSmallMedium.copyWith(color: text), + ), ], ), ); diff --git a/lib/src/core/design_system/templates/profile_page_template.dart b/lib/src/core/design_system/templates/profile_page_template.dart index 602ec8f5..5f3af52e 100644 --- a/lib/src/core/design_system/templates/profile_page_template.dart +++ b/lib/src/core/design_system/templates/profile_page_template.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/icons.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/profile_action_buttons.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/profile_avatar.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/profile_info.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/profile_stats.dart'; -import 'package:sparksocial/src/core/design_system/components/organisms/sticky_profile_tab_bar.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; +import 'package:spark/src/core/design_system/components/atoms/icons.dart'; +import 'package:spark/src/core/design_system/components/molecules/profile_action_buttons.dart'; +import 'package:spark/src/core/design_system/components/molecules/profile_avatar.dart'; +import 'package:spark/src/core/design_system/components/molecules/profile_info.dart'; +import 'package:spark/src/core/design_system/components/molecules/profile_stats.dart'; +import 'package:spark/src/core/design_system/components/organisms/sticky_profile_tab_bar.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class ProfilePageTemplate extends StatelessWidget { const ProfilePageTemplate({ @@ -143,7 +143,10 @@ class ProfilePageTemplate extends StatelessWidget { pinned: true, delegate: StickyProfileTabBar(child: tabsWidget), ), - if (contentSlivers != null) ...contentSlivers! else SliverFillRemaining(child: contentWidget), + if (contentSlivers != null) + ...contentSlivers! + else + SliverFillRemaining(child: contentWidget), ], ), ), @@ -266,7 +269,11 @@ class _ProfileHeaderSection extends StatelessWidget { Row( children: [ Skeleton.keep( - child: Text(displayName, overflow: TextOverflow.ellipsis, style: AppTypography.textLargeBold), + child: Text( + displayName, + overflow: TextOverflow.ellipsis, + style: AppTypography.textLargeBold, + ), ), if (isEarlySupporter) GestureDetector( @@ -295,7 +302,8 @@ class _ProfileHeaderSection extends StatelessWidget { ), ], ), - if ((description?.isNotEmpty ?? false) || (links?.isNotEmpty ?? false)) ...[ + if ((description?.isNotEmpty ?? false) || + (links?.isNotEmpty ?? false)) ...[ const SizedBox(height: 12), ProfileInfo( displayName: displayName, 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 544c7ddb..02cc147f 100644 --- a/lib/src/core/design_system/templates/recording_page_template.dart +++ b/lib/src/core/design_system/templates/recording_page_template.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/recording_button.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/recording_timer.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; +import 'package:spark/src/core/design_system/components/molecules/recording_button.dart'; +import 'package:spark/src/core/design_system/components/molecules/recording_timer.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; class RecordingPageTemplate extends StatelessWidget { const RecordingPageTemplate({ @@ -33,7 +33,6 @@ class RecordingPageTemplate extends StatelessWidget { final size = MediaQuery.sizeOf(context); // Calculate scale based on camera aspect ratio and screen aspect ratio - // For camera package v0.7.0+, this is: size.aspectRatio * camera.aspectRatio var scale = size.aspectRatio * aspectRatio; // To prevent scaling down, invert the value if scale < 1 @@ -145,7 +144,10 @@ class _BottomOverlay extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - if (onFlipCamera != null) _FlipCameraButton(onPressed: onFlipCamera!) else const SizedBox(width: 72), + if (onFlipCamera != null) + _FlipCameraButton(onPressed: onFlipCamera!) + else + const SizedBox(width: 72), recordingButton, const SizedBox(width: 72), ], diff --git a/lib/src/core/design_system/templates/video_review_page_template.dart b/lib/src/core/design_system/templates/video_review_page_template.dart index 93415e95..ef5122eb 100644 --- a/lib/src/core/design_system/templates/video_review_page_template.dart +++ b/lib/src/core/design_system/templates/video_review_page_template.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/buttons/long_button.dart'; -import 'package:sparksocial/src/core/design_system/components/atoms/toggles/toggle.dart'; -import 'package:sparksocial/src/core/design_system/components/molecules/glass_input.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/shapes.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/app_leading_button.dart'; +import 'package:spark/src/core/design_system/components/atoms/buttons/long_button.dart'; +import 'package:spark/src/core/design_system/components/atoms/toggles/toggle.dart'; +import 'package:spark/src/core/design_system/components/molecules/glass_input.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/shapes.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; /// Design-only template for the Video Review flow. /// @@ -31,7 +31,8 @@ class VideoReviewPageTemplate extends StatelessWidget { }); final String title; - final VoidCallback onBack; // Kept for API symmetry; AppLeadingButton handles back internally. + final VoidCallback + onBack; // Kept for API symmetry; AppLeadingButton handles back internally. final Widget videoPreview; final VoidCallback onAltEdit; final TextEditingController descriptionController; @@ -55,7 +56,10 @@ class VideoReviewPageTemplate extends StatelessWidget { appBar: AppBar( backgroundColor: backgroundColor ?? colorScheme.surface, elevation: 0, - leading: AppLeadingButton(color: theme.textTheme.titleLarge?.color, tooltip: 'Back'), + leading: AppLeadingButton( + color: theme.textTheme.titleLarge?.color, + tooltip: 'Back', + ), title: Text(title), centerTitle: false, ), @@ -106,7 +110,10 @@ class VideoReviewPageTemplate extends StatelessWidget { child: const SizedBox( width: 20, height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: AppColors.greyWhite), + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColors.greyWhite, + ), ), ) : LongButton(label: postLabel, onPressed: onPost), @@ -137,7 +144,10 @@ class _VideoPreview extends StatelessWidget { width: AppShapes.squircleBorderWidth, color: Colors.white.withAlpha(AppShapes.squircleBorderAlpha), ); - final ShapeBorder shape = RoundedSuperellipseBorder(side: side, borderRadius: radius); + final ShapeBorder shape = RoundedSuperellipseBorder( + side: side, + borderRadius: radius, + ); return AspectRatio( aspectRatio: aspectRatio, @@ -156,13 +166,21 @@ class _VideoPreview extends StatelessWidget { child: GestureDetector( onTap: onAltEdit, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.black.withAlpha(100), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.white.withAlpha(38)), ), - child: Text('ALT', style: AppTypography.textSmallBold.copyWith(color: AppColors.greyWhite)), + child: Text( + 'ALT', + style: AppTypography.textSmallBold.copyWith( + color: AppColors.greyWhite, + ), + ), ), ), ), @@ -198,7 +216,9 @@ class _DescriptionSection extends StatelessWidget { alignment: Alignment.centerRight, child: Text( '$count/$maxChars', - style: AppTypography.textSmallMedium.copyWith(color: Colors.white.withAlpha(160)), + style: AppTypography.textSmallMedium.copyWith( + color: Colors.white.withAlpha(160), + ), ), ), ], @@ -227,7 +247,9 @@ class _CrossPostSection extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), title: Text( 'Post to Bluesky', - style: AppTypography.textMediumBold.copyWith(color: AppColors.greyWhite), + style: AppTypography.textMediumBold.copyWith( + color: AppColors.greyWhite, + ), ), trailing: Toggle(value: value, onChanged: onChanged), onTap: () => onChanged(!value), diff --git a/lib/src/core/design_system/theme/app_theme.dart b/lib/src/core/design_system/theme/app_theme.dart index d15e7ad2..80f8bd7c 100644 --- a/lib/src/core/design_system/theme/app_theme.dart +++ b/lib/src/core/design_system/theme/app_theme.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/theme/color_scheme.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/theme/color_scheme.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; class AppTheme { AppTheme._(); @@ -19,7 +19,9 @@ class AppTheme { backgroundColor: AppColorScheme.light.surface, foregroundColor: AppColorScheme.light.onSurface, iconTheme: IconThemeData(color: AppColorScheme.light.onSurface), - titleTextStyle: AppTypography.textLargeBold.copyWith(color: AppColorScheme.light.onSurface), + titleTextStyle: AppTypography.textLargeBold.copyWith( + color: AppColorScheme.light.onSurface, + ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( @@ -50,7 +52,10 @@ class AppTheme { inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: AppColorScheme.light.surfaceContainerHighest, - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -103,7 +108,9 @@ class AppTheme { backgroundColor: AppColorScheme.dark.surface, foregroundColor: AppColorScheme.dark.onSurface, iconTheme: IconThemeData(color: AppColorScheme.dark.onSurface), - titleTextStyle: AppTypography.textLargeBold.copyWith(color: AppColorScheme.dark.onSurface), + titleTextStyle: AppTypography.textLargeBold.copyWith( + color: AppColorScheme.dark.onSurface, + ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( @@ -134,7 +141,10 @@ class AppTheme { inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: AppColorScheme.dark.surfaceContainerHighest, - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, diff --git a/lib/src/core/design_system/theme/color_scheme.dart b/lib/src/core/design_system/theme/color_scheme.dart index 6ec50109..99a79be1 100644 --- a/lib/src/core/design_system/theme/color_scheme.dart +++ b/lib/src/core/design_system/theme/color_scheme.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/tokens/colors.dart'; +import 'package:spark/src/core/design_system/tokens/colors.dart'; /// Creates color schemes for light and dark themes using design tokens class AppColorScheme { diff --git a/lib/src/core/design_system/theme/text_theme.dart b/lib/src/core/design_system/theme/text_theme.dart index 29f356be..667c6353 100644 --- a/lib/src/core/design_system/theme/text_theme.dart +++ b/lib/src/core/design_system/theme/text_theme.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sparksocial/src/core/design_system/tokens/typography.dart'; +import 'package:spark/src/core/design_system/tokens/typography.dart'; /// Creates text themes using our typography tokens /// This ensures consistent typography throughout the app diff --git a/lib/src/core/di/service_locator.dart b/lib/src/core/di/service_locator.dart index 48953d93..09fa9942 100644 --- a/lib/src/core/di/service_locator.dart +++ b/lib/src/core/di/service_locator.dart @@ -1,30 +1,30 @@ import 'package:get_it/get_it.dart'; -import 'package:sparksocial/src/core/auth/data/repositories/auth_repository_impl.dart'; -import 'package:sparksocial/src/core/auth/data/repositories/onboarding_repository.dart'; -import 'package:sparksocial/src/core/auth/data/repositories/onboarding_repository_impl.dart'; -import 'package:sparksocial/src/core/network/atproto/atproto.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/actor_repository_impl.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/graph_repository_impl.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/pref_repository.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/pref_repository_impl.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/sound_repository.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/sound_repository_impl.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/sprk_repository_impl.dart'; -import 'package:sparksocial/src/core/network/atproto/data/repositories/story_repository_impl.dart'; -import 'package:sparksocial/src/core/network/messages/data/repository/messages_repository.dart'; -import 'package:sparksocial/src/core/network/messages/data/repository/messages_repository_xrpc.dart'; -import 'package:sparksocial/src/core/network/xrpc/service_auth_helper.dart'; -import 'package:sparksocial/src/core/pro_video_editor/pro_video_editor_repository.dart'; -import 'package:sparksocial/src/core/pro_video_editor/pro_video_editor_repository_impl.dart'; -import 'package:sparksocial/src/core/storage/cache/download_manager_interface.dart'; -import 'package:sparksocial/src/core/storage/storage.dart'; -import 'package:sparksocial/src/core/ui/theme/data/repositories/theme_repository.dart'; -import 'package:sparksocial/src/core/ui/theme/data/repositories/theme_repository_impl.dart'; -import 'package:sparksocial/src/core/utils/logging/logging.dart'; -import 'package:sparksocial/src/features/auth/auth.dart'; +import 'package:spark/src/core/auth/data/repositories/auth_repository_impl.dart'; +import 'package:spark/src/core/auth/data/repositories/onboarding_repository.dart'; +import 'package:spark/src/core/auth/data/repositories/onboarding_repository_impl.dart'; +import 'package:spark/src/core/network/atproto/atproto.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/actor_repository_impl.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/graph_repository_impl.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/pref_repository.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/pref_repository_impl.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/sound_repository.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/sound_repository_impl.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/sprk_repository_impl.dart'; +import 'package:spark/src/core/network/atproto/data/repositories/story_repository_impl.dart'; +import 'package:spark/src/core/network/messages/data/repository/messages_repository.dart'; +import 'package:spark/src/core/network/messages/data/repository/messages_repository_xrpc.dart'; +import 'package:spark/src/core/network/xrpc/service_auth_helper.dart'; +import 'package:spark/src/core/pro_video_editor/pro_video_editor_repository.dart'; +import 'package:spark/src/core/pro_video_editor/pro_video_editor_repository_impl.dart'; +import 'package:spark/src/core/storage/cache/download_manager_interface.dart'; +import 'package:spark/src/core/storage/storage.dart'; +import 'package:spark/src/core/ui/theme/data/repositories/theme_repository.dart'; +import 'package:spark/src/core/ui/theme/data/repositories/theme_repository_impl.dart'; +import 'package:spark/src/core/utils/logging/logging.dart'; +import 'package:spark/src/features/auth/auth.dart'; // This is the ONLY PLACE IN THE ENTIRE APP where implementations are imported -// All the other files should import interfaces only (polymorphism) to keep everything decoupled +// All the other files should import interfaces only (polymorphism). /// Global ServiceLocator instance final GetIt sl = GetIt.instance; @@ -51,42 +51,67 @@ Future initServiceLocator() async { sl.registerSingleton(AuthRepositoryImpl()); // Register service auth helper for XRPC - sl.registerSingleton(ServiceAuthHelper(sl())); + sl.registerSingleton( + ServiceAuthHelper(sl()), + ); // Register Chat dependencies with XRPC implementation - sl.registerSingleton(MessagesRepositoryXrpc(sl())); + sl.registerSingleton( + MessagesRepositoryXrpc(sl()), + ); // Register SprkRepository with its interface - sl.registerSingleton(SprkRepositoryImpl(sl())); + sl.registerSingleton( + SprkRepositoryImpl(sl()), + ); // Register PrefRepository - sl.registerSingleton(PrefRepositoryImpl(sl())); + sl.registerSingleton( + PrefRepositoryImpl(sl()), + ); // Register identity repository - sl.registerSingleton(IdentityRepositoryImpl(sl())); + sl.registerSingleton( + IdentityRepositoryImpl(sl()), + ); // Register theme repository - sl.registerSingleton(ThemeRepositoryImpl(sl())); + sl.registerSingleton( + ThemeRepositoryImpl(sl()), + ); // Register ActorRepository - sl.registerSingleton(ActorRepositoryImpl(sl.get())); + sl.registerSingleton( + ActorRepositoryImpl(sl.get()), + ); // Register GraphRepository - sl.registerSingleton(GraphRepositoryImpl(sl.get())); + sl.registerSingleton( + GraphRepositoryImpl(sl.get()), + ); // Register StoryRepository - sl.registerSingleton(StoryRepositoryImpl(sl.get())); + sl.registerSingleton( + StoryRepositoryImpl(sl.get()), + ); // Register SoundRepository - sl.registerSingleton(SoundRepositoryImpl(sl.get())); + sl.registerSingleton( + SoundRepositoryImpl(sl.get()), + ); await downloadManager.init(); // Register OnboardingRepository sl.registerLazySingleton( - () => OnboardingRepositoryImpl(repoRepository: sl().repo, authRepository: sl()), + () => OnboardingRepositoryImpl( + repoRepository: sl().repo, + authRepository: sl(), + ), ); // Register ProVideoEditorRepository (image/video editing abstraction) - sl.registerSingleton(const ProVideoEditorRepositoryImpl()); + sl.registerSingleton( + const ProVideoEditorRepositoryImpl(), + ); } diff --git a/lib/src/core/l10n/app_localizations.dart b/lib/src/core/l10n/app_localizations.dart index 609ce929..2e04266b 100644 --- a/lib/src/core/l10n/app_localizations.dart +++ b/lib/src/core/l10n/app_localizations.dart @@ -61,7 +61,8 @@ import 'app_localizations_en.dart'; /// be consistent with the languages listed in the AppLocalizations.supportedLocales /// property. abstract class AppLocalizations { - AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; @@ -69,7 +70,8 @@ abstract class AppLocalizations { return Localizations.of(context, AppLocalizations)!; } - static const LocalizationsDelegate delegate = _AppLocalizationsDelegate(); + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -81,12 +83,13 @@ abstract class AppLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [Locale('en')]; @@ -170,7 +173,8 @@ abstract class AppLocalizations { String get optionsPanelClose; } -class _AppLocalizationsDelegate extends LocalizationsDelegate { +class _AppLocalizationsDelegate + extends LocalizationsDelegate { const _AppLocalizationsDelegate(); @override @@ -179,7 +183,8 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate } @override - bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); + bool isSupported(Locale locale) => + ['en'].contains(locale.languageCode); @override bool shouldReload(_AppLocalizationsDelegate old) => false; diff --git a/lib/src/core/media/create_media_actions.dart b/lib/src/core/media/create_media_actions.dart index be46d791..637621bf 100644 --- a/lib/src/core/media/create_media_actions.dart +++ b/lib/src/core/media/create_media_actions.dart @@ -5,18 +5,21 @@ 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:sparksocial/src/core/pro_video_editor/pro_video_editor_repository.dart'; -import 'package:sparksocial/src/core/routing/app_router.dart'; +import 'package:spark/src/core/pro_video_editor/pro_video_editor_repository.dart'; +import 'package:spark/src/core/routing/app_router.dart'; /// Centralized factories for the create media actions used across the app. /// /// These return closures compatible with typical UI callbacks so that -/// pages can simply pass them into [showCreateMediaSheet] without duplicating logic. +/// pages can just pass them to [showCreateMediaSheet] without duplicate logic. class CreateMediaActions { const CreateMediaActions._(); /// Record flow: camera capture -> editor -> review. - static VoidCallback onRecord(BuildContext context, {required bool storyMode}) { + static VoidCallback onRecord( + BuildContext context, { + required bool storyMode, + }) { return () async { if (!context.mounted) return; await context.router.push(RecordingRoute(storyMode: storyMode)); @@ -24,7 +27,10 @@ class CreateMediaActions { } /// Upload video flow: pick from gallery -> open editor -> review (story/post mode decided here). - static VoidCallback onUploadVideo(BuildContext context, {required bool storyMode}) { + static VoidCallback onUploadVideo( + BuildContext context, { + required bool storyMode, + }) { return () async { final pickedVideo = await ImagePicker().pickVideo( source: ImageSource.gallery, @@ -32,10 +38,15 @@ class CreateMediaActions { ); if (pickedVideo != null && context.mounted) { final editorVideo = EditorVideo.file(File(pickedVideo.path)); - final result = await GetIt.I().openVideoEditor(context, editorVideo); + final result = await GetIt.I() + .openVideoEditor(context, editorVideo); if (result != null && context.mounted) { await context.router.push( - VideoReviewRoute(videoPath: result.video.path, storyMode: storyMode, soundRef: result.soundRef), + VideoReviewRoute( + videoPath: result.video.path, + storyMode: storyMode, + soundRef: result.soundRef, + ), ); } } @@ -43,7 +54,10 @@ class CreateMediaActions { } /// Upload images flow: multi-pick -> image review (story/post mode decided here). - static VoidCallback onUploadImages(BuildContext context, {required bool storyMode}) { + static VoidCallback onUploadImages( + BuildContext context, { + required bool storyMode, + }) { return () async { final pickedImages = await ImagePicker().pickMultiImage(limit: 12); if (context.mounted && pickedImages.isNotEmpty) { diff --git a/lib/src/core/network/atproto/data/adapters/adapters.dart b/lib/src/core/network/atproto/data/adapters/adapters.dart index 4c089653..7f3bfd22 100644 --- a/lib/src/core/network/atproto/data/adapters/adapters.dart +++ b/lib/src/core/network/atproto/data/adapters/adapters.dart @@ -1,3 +1,3 @@ -export 'package:sparksocial/src/core/network/atproto/data/adapters/bsky/actor_adapter.dart'; -export 'package:sparksocial/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart'; -export 'package:sparksocial/src/core/network/atproto/data/adapters/bsky/repo_adapter.dart'; +export 'package:spark/src/core/network/atproto/data/adapters/bsky/actor_adapter.dart'; +export 'package:spark/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart'; +export 'package:spark/src/core/network/atproto/data/adapters/bsky/repo_adapter.dart'; diff --git a/lib/src/core/network/atproto/data/adapters/bsky/actor_adapter.dart b/lib/src/core/network/atproto/data/adapters/bsky/actor_adapter.dart index f6cc8e3d..5eac0691 100644 --- a/lib/src/core/network/atproto/data/adapters/bsky/actor_adapter.dart +++ b/lib/src/core/network/atproto/data/adapters/bsky/actor_adapter.dart @@ -1,5 +1,5 @@ import 'package:bluesky/bluesky.dart' as bsky; -import 'package:sparksocial/src/core/network/atproto/data/models/models.dart'; +import 'package:spark/src/core/network/atproto/data/models/models.dart'; /// Adapter for Bluesky actor models <-> Spark actor models /// @@ -8,35 +8,49 @@ import 'package:sparksocial/src/core/network/atproto/data/models/models.dart'; class BskyActorAdapter { const BskyActorAdapter(); - // ============================================================================ + // =========================================================================== // Bluesky -> Spark Conversions - // ============================================================================ + // =========================================================================== /// Get a single profile from Bluesky and convert to Spark format - Future getProfileFromBluesky(bsky.Bluesky bluesky, String did) async { + Future getProfileFromBluesky( + bsky.Bluesky bluesky, + String did, + ) async { final profile = await bluesky.actor.getProfile(actor: did); return convertBskyProfileToSpark(profile.data.toJson()); } /// Get multiple profiles from Bluesky and convert to Spark format - Future> getProfilesFromBluesky(bsky.Bluesky bluesky, List dids) async { + Future> getProfilesFromBluesky( + bsky.Bluesky bluesky, + List dids, + ) async { final profiles = await bluesky.actor.getProfiles(actors: dids); - return profiles.data.profiles.map((p) => convertBskyProfileToSpark(p.toJson())).toList(); + return profiles.data.profiles + .map((p) => convertBskyProfileToSpark(p.toJson())) + .toList(); } /// Convert a Bluesky profile JSON to Spark ProfileViewDetailed - ProfileViewDetailed convertBskyProfileToSpark(Map bskyProfileJson) { + ProfileViewDetailed convertBskyProfileToSpark( + Map bskyProfileJson, + ) { // Bluesky and Spark profile structures are compatible, just parse directly return ProfileViewDetailed.fromJson(bskyProfileJson); } /// Convert a Bluesky ProfileView JSON to Spark ProfileView - ProfileView convertBskyProfileViewToSpark(Map bskyProfileJson) { + ProfileView convertBskyProfileViewToSpark( + Map bskyProfileJson, + ) { return ProfileView.fromJson(bskyProfileJson); } /// Convert a Bluesky ProfileViewBasic JSON to Spark ProfileViewBasic - ProfileViewBasic convertBskyProfileViewBasicToSpark(Map bskyProfileJson) { + ProfileViewBasic convertBskyProfileViewBasicToSpark( + Map bskyProfileJson, + ) { return ProfileViewBasic.fromJson(bskyProfileJson); } } diff --git a/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart b/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart index e58529c6..600a8f67 100644 --- a/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart +++ b/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart @@ -4,8 +4,9 @@ import 'package:bluesky/app_bsky_feed_defs.dart' as bsky_defs; import 'package:bluesky/app_bsky_feed_getpostthread.dart'; import 'package:bluesky/app_bsky_feed_post.dart'; import 'package:bluesky/app_bsky_richtext_facet.dart'; -import 'package:sparksocial/src/core/network/atproto/data/models/models.dart' hide ReplyRef; -import 'package:sparksocial/src/core/utils/json_utils.dart'; +import 'package:spark/src/core/network/atproto/data/models/models.dart' + hide ReplyRef; +import 'package:spark/src/core/utils/json_utils.dart'; /// Adapter for Bluesky feed models <-> Spark feed models /// @@ -15,9 +16,9 @@ import 'package:sparksocial/src/core/utils/json_utils.dart'; class BskyFeedAdapter { const BskyFeedAdapter(); - // ============================================================================ + // =========================================================================== // Bluesky Embed Filtering & Sanitization - // ============================================================================ + // =========================================================================== /// List of unsupported embed types that should be filtered out static const unsupportedEmbedTypes = [ @@ -39,12 +40,14 @@ class BskyFeedAdapter { final embedType = embedData[r'$type'] as String?; // Check if this is a record embed - if (embedType == 'app.bsky.embed.record#view' && embedData['record'] != null) { + if (embedType == 'app.bsky.embed.record#view' && + embedData['record'] != null) { return true; } // Check if this is a recordWithMedia embed - if (embedType == 'app.bsky.embed.recordWithMedia#view' && embedData['record'] != null) { + if (embedType == 'app.bsky.embed.recordWithMedia#view' && + embedData['record'] != null) { return true; } @@ -65,14 +68,16 @@ class BskyFeedAdapter { } // Check post-level embed - if (postJson['embed'] != null && postJson['embed'] is Map) { + if (postJson['embed'] != null && + postJson['embed'] is Map) { if (checkForRecordEmbed(postJson['embed'] as Map)) { return true; } } // Check record-level embed - if (postJson['record'] != null && postJson['record'] is Map) { + if (postJson['record'] != null && + postJson['record'] is Map) { final record = postJson['record'] as Map; if (record['embed'] != null && record['embed'] is Map) { if (checkForRecordEmbed(record['embed'] as Map)) { @@ -106,7 +111,8 @@ class BskyFeedAdapter { } // If it's a record embed, check the record data - if (embedJson[r'$type'] == 'app.bsky.embed.record#view' && embedJson['record'] != null) { + if (embedJson[r'$type'] == 'app.bsky.embed.record#view' && + embedJson['record'] != null) { final recordJson = embedJson['record'] as Map; // Filter out unsupported record embed types @@ -133,7 +139,8 @@ class BskyFeedAdapter { for (final nestedEmbed in embedsList) { if (nestedEmbed is Map) { // Check external embeds in the nested embeds - if (nestedEmbed[r'$type'] == 'app.bsky.embed.external#view' && nestedEmbed['cid'] == null) { + if (nestedEmbed[r'$type'] == 'app.bsky.embed.external#view' && + nestedEmbed['cid'] == null) { postViewJson.remove('embed'); return; } @@ -174,12 +181,18 @@ class BskyFeedAdapter { // Recursive validation for any remaining embed structures if (postViewJson['embed'] != null) { - _validateRecordViewInEmbed(postViewJson['embed'] as Map, postViewJson); + _validateRecordViewInEmbed( + postViewJson['embed'] as Map, + postViewJson, + ); } } /// Recursively validate record views in embed structures - void _validateRecordViewInEmbed(Map embedData, Map postViewJson) { + void _validateRecordViewInEmbed( + Map embedData, + Map postViewJson, + ) { final embedType = embedData[r'$type'] as String?; // Filter out unsupported record embed types @@ -206,16 +219,19 @@ class BskyFeedAdapter { } else if (value is List) { for (var i = 0; i < value.length; i++) { if (value[i] is Map) { - _validateRecordViewInEmbed(value[i] as Map, postViewJson); + _validateRecordViewInEmbed( + value[i] as Map, + postViewJson, + ); } } } }); } - // ============================================================================ + // =========================================================================== // Bluesky -> Spark Conversions - // ============================================================================ + // =========================================================================== /// Transforms Bluesky images (multiple) to Spark single image format /// For comments/replies, only the first image should be used void _transformBskyImagesToSingleSparkImage(Map mediaJson) { @@ -233,7 +249,10 @@ class BskyFeedAdapter { } /// Convert Bluesky PostView JSON to Spark PostView JSON format - void convertPostViewJson(Map post, {bool isNestedReply = false}) { + void convertPostViewJson( + Map post, { + bool isNestedReply = false, + }) { if (post.containsKey('embed')) { post['media'] = post['embed']; post.remove('embed'); @@ -307,7 +326,8 @@ class BskyFeedAdapter { root ..clear() ..addAll({r'$type': rootType, 'post': postViewData}); - } else if (rootType == 'app.bsky.feed.defs#notFoundPost' || rootType == 'app.bsky.feed.defs#blockedPost') { + } else if (rootType == 'app.bsky.feed.defs#notFoundPost' || + rootType == 'app.bsky.feed.defs#blockedPost') { // Already in correct format } else if (root.containsKey('post')) { final rootPost = root['post']; @@ -329,7 +349,8 @@ class BskyFeedAdapter { parent ..clear() ..addAll({r'$type': parentType, 'post': postViewData}); - } else if (parentType == 'app.bsky.feed.defs#notFoundPost' || parentType == 'app.bsky.feed.defs#blockedPost') { + } else if (parentType == 'app.bsky.feed.defs#notFoundPost' || + parentType == 'app.bsky.feed.defs#blockedPost') { // Already in correct format } else if (parent.containsKey('post')) { final parentPost = parent['post']; @@ -359,9 +380,9 @@ class BskyFeedAdapter { } } - // ============================================================================ + // =========================================================================== // Spark -> Bluesky Conversions - // ============================================================================ + // =========================================================================== /// Convert Spark images to Bluesky images List convertImages(List sparkImages) { @@ -385,17 +406,23 @@ class BskyFeedAdapter { alt: alt ?? '', image: image, ); - return UFeedPostEmbed.embedImages(data: EmbedImages(images: [bskyImage])); + return UFeedPostEmbed.embedImages( + data: EmbedImages(images: [bskyImage]), + ); case MediaImages(:final images): // Convert multiple Spark images to Bluesky embed images final bskyImages = convertImages(images); - return UFeedPostEmbed.embedImages(data: EmbedImages(images: bskyImages)); + return UFeedPostEmbed.embedImages( + data: EmbedImages(images: bskyImages), + ); case MediaBskyImages(:final images): // Already in Bluesky format, convert to embed final bskyImages = convertImages(images); - return UFeedPostEmbed.embedImages(data: EmbedImages(images: bskyImages)); + return UFeedPostEmbed.embedImages( + data: EmbedImages(images: bskyImages), + ); case MediaVideo(): case MediaBskyVideo(): @@ -417,7 +444,9 @@ class BskyFeedAdapter { return FeedPostRecord( text: text, createdAt: createdAt, - embed: images != null && images.isNotEmpty ? UFeedPostEmbed.embedImages(data: EmbedImages(images: images)) : null, + embed: images != null && images.isNotEmpty + ? UFeedPostEmbed.embedImages(data: EmbedImages(images: images)) + : null, facets: facets, ); } @@ -446,17 +475,27 @@ class BskyFeedAdapter { required int byteStart, }) { return RichtextFacet( - index: RichtextFacetByteSlice(byteStart: byteStart, byteEnd: byteStart + linkUrl.length), - features: [URichtextFacetFeatures.richtextFacetLink(data: RichtextFacetLink(uri: linkUrl))], + index: RichtextFacetByteSlice( + byteStart: byteStart, + byteEnd: byteStart + linkUrl.length, + ), + features: [ + URichtextFacetFeatures.richtextFacetLink( + data: RichtextFacetLink(uri: linkUrl), + ), + ], ); } - // ============================================================================ + // =========================================================================== // Bluesky Thread Conversion - // ============================================================================ + // =========================================================================== /// Convert a Bluesky parent thread to Spark Thread - Thread? _convertParentToThread(bsky_defs.UThreadViewPostParent parent, AtUri uri) { + Thread? _convertParentToThread( + bsky_defs.UThreadViewPostParent parent, + AtUri uri, + ) { switch (parent) { case bsky_defs.UThreadViewPostParentThreadViewPost(:final data): return convertBskyThreadToSparkThread( @@ -466,7 +505,11 @@ class BskyFeedAdapter { case bsky_defs.UThreadViewPostParentNotFoundPost(:final data): return Thread.notFoundPost(uri: data.uri, notFound: true); case bsky_defs.UThreadViewPostParentBlockedPost(:final data): - return Thread.blockedPost(uri: data.uri, blocked: true, author: BlockedAuthor.fromJson(data.author.toJson())); + return Thread.blockedPost( + uri: data.uri, + blocked: true, + author: BlockedAuthor.fromJson(data.author.toJson()), + ); case bsky_defs.UThreadViewPostParentUnknown(): return null; } @@ -490,7 +533,8 @@ class BskyFeedAdapter { } final postJson = data.post.copyWith(embed: embed); - // Create PostView with deep copy - required because we modify nested structures like embeds + // Create PostView with deep copy + //Required because we modify nested structures like embeds final postViewJson = deepCopyJson(postJson.toJson()); // Ensure required fields are not null @@ -519,7 +563,7 @@ class BskyFeedAdapter { throw Exception('Author handle is null'); } - // Sanitize embeds using the adapter (handles all Bluesky-specific filtering) + // Sanitize embeds using adapter (handles Bluesky-specific filtering) sanitizeBskyPostViewJson(postViewJson); // Convert from Bluesky format to Spark format @@ -527,22 +571,32 @@ class BskyFeedAdapter { final sparkThread = Thread.threadViewPost( post: ThreadPost.post(post: PostView.fromJson(postViewJson)), - parent: data.parent != null ? _convertParentToThread(data.parent!, uri) : null, + parent: data.parent != null + ? _convertParentToThread(data.parent!, uri) + : null, replies: data.replies ?.map((reply) { switch (reply) { - case bsky_defs.UThreadViewPostRepliesThreadViewPost(:final data): - // Filter out replies with EmbedViewRecord using the adapter + case bsky_defs.UThreadViewPostRepliesThreadViewPost( + :final data, + ): + // Filter out replies with EmbedViewRecord using adapter if (shouldFilterReply(data.post)) { return null; } return convertBskyThreadToSparkThread( - thread: UFeedGetPostThreadThread.threadViewPost(data: data), + thread: UFeedGetPostThreadThread.threadViewPost( + data: data, + ), uri: data.post.uri, ); - case bsky_defs.UThreadViewPostRepliesNotFoundPost(:final data): + case bsky_defs.UThreadViewPostRepliesNotFoundPost( + :final data, + ): return Thread.notFoundPost(uri: data.uri, notFound: true); - case bsky_defs.UThreadViewPostRepliesBlockedPost(:final data): + case bsky_defs.UThreadViewPostRepliesBlockedPost( + :final data, + ): return Thread.blockedPost( uri: data.uri, blocked: true, @@ -563,15 +617,19 @@ class BskyFeedAdapter { case UFeedGetPostThreadThreadNotFoundPost(:final data): return Thread.notFoundPost(uri: data.uri, notFound: true); case UFeedGetPostThreadThreadBlockedPost(:final data): - return Thread.blockedPost(uri: data.uri, blocked: true, author: BlockedAuthor.fromJson(data.author.toJson())); + return Thread.blockedPost( + uri: data.uri, + blocked: true, + author: BlockedAuthor.fromJson(data.author.toJson()), + ); default: throw Exception('Unsupported thread type: ${thread.runtimeType}'); } } - // ============================================================================ + // =========================================================================== // Bluesky Feed Processing - // ============================================================================ + // =========================================================================== /// Check if a FeedViewPost has supported media bool _feedViewPostHasMedia(FeedViewPost feedViewPost) { @@ -589,28 +647,19 @@ class BskyFeedAdapter { ); } - /// Check if a raw Bluesky FeedViewPost is a repost - /// Bluesky reposts have a "reason" field with type "app.bsky.feed.defs#reasonRepost" - bool _isBskyRepost(bsky_defs.FeedViewPost feedViewPost) { - return feedViewPost.reason != null; - } - /// Process raw Bluesky FeedViewPost list and convert to Spark format - /// Handles: deep copy, conversion, parsing, and filtering (reposts, replies + media) + /// Handles deep copy, conversion, parsing, and filtering + /// (reposts, replies, media) ({List posts, String? cursor}) processBskyAuthorFeed({ required List rawFeed, required String? cursor, - void Function(String message, {Object? error, StackTrace? stackTrace})? onError, + void Function(String message, {Object? error, StackTrace? stackTrace})? + onError, }) { final feedPosts = []; for (var i = 0; i < rawFeed.length; i++) { try { - // Filter out reposts - Bluesky doesn't have a getReposts equivalent endpoint - if (_isBskyRepost(rawFeed[i])) { - continue; - } - // Deep copy to make mutable final rawPost = deepCopyJson(rawFeed[i].toJson()); @@ -624,23 +673,27 @@ class BskyFeedAdapter { final parsedPost = FeedViewPost.fromJson(rawPost); feedPosts.add(parsedPost); } catch (e, stackTrace) { - onError?.call('Failed to parse bsky feed view #$i', error: e, stackTrace: stackTrace); + onError?.call( + 'Failed to parse bsky feed view #$i', + error: e, + stackTrace: stackTrace, + ); } } // Convert to Spark format and filter - final convertedPosts = feedPosts.map((post) => post.toSparkFeedViewPost()).toList(); - final filteredPosts = convertedPosts.where((post) => !_feedViewPostIsReply(post)).where(_feedViewPostHasMedia).toList(); + final convertedPosts = feedPosts + .map((post) => post.toSparkFeedViewPost()) + .toList(); + final filteredPosts = convertedPosts + .where((post) => !_feedViewPostIsReply(post)) + .where(_feedViewPostHasMedia) + .toList(); return (posts: filteredPosts, cursor: cursor); } - /// Check if a raw Bluesky feed item JSON is a repost - bool _isBskyRepostJson(Map itemMap) { - return itemMap.containsKey('reason') && itemMap['reason'] != null; - } - - /// Process raw Bluesky feed item list (from getFeed API) and convert to Spark format + /// Process raw Bluesky feed items (from getFeed API), convert to Spark format /// Handles: conversion, parsing, and filtering (reposts, replies + media) List processBskyFeedItems({ required List feedData, @@ -652,11 +705,6 @@ class BskyFeedAdapter { try { final itemMap = item as Map; - // Filter out reposts - Bluesky doesn't have a getReposts equivalent endpoint - if (_isBskyRepostJson(itemMap)) { - continue; - } - // Convert the JSON to Spark format convertFeedViewPostJson(itemMap); @@ -674,12 +722,18 @@ class BskyFeedAdapter { final convertedPost = feedViewPost.toSparkFeedViewPost(); feedPosts.add(convertedPost); } catch (e, stackTrace) { - onWarning?.call('Failed to parse feed item, skipping: $e', stackTrace: stackTrace); + onWarning?.call( + 'Failed to parse feed item, skipping: $e', + stackTrace: stackTrace, + ); } } // Filter out replies and posts without media - return feedPosts.where((post) => !_feedViewPostIsReply(post)).where(_feedViewPostHasMedia).toList(); + return feedPosts + .where((post) => !_feedViewPostIsReply(post)) + .where(_feedViewPostHasMedia) + .toList(); } /// Process raw Bluesky PostView list and convert to Spark format @@ -698,7 +752,9 @@ class BskyFeedAdapter { // Parse and convert to Spark format final parsedPosts = rawPostsJson.map(PostView.fromJson).toList(); - final sparkPosts = parsedPosts.map((post) => post.toSparkPostView()).toList(); + final sparkPosts = parsedPosts + .map((post) => post.toSparkPostView()) + .toList(); // Optionally filter by media if (filterByMedia) { @@ -748,16 +804,18 @@ extension BskyPostRecordAdapter on BskyPostRecord { extension BskyRecordAdapter on Record { Record toSparkRecord() { return when( - post: (caption, createdAt, reply, langs, tags, selfLabels, media, sound) => PostRecord( - caption: caption, - createdAt: createdAt, - reply: reply, - langs: langs, - tags: tags, - selfLabels: selfLabels, - media: media, - sound: sound, - ), + post: + (caption, createdAt, reply, langs, tags, selfLabels, media, sound) => + PostRecord( + caption: caption, + createdAt: createdAt, + reply: reply, + langs: langs, + tags: tags, + selfLabels: selfLabels, + media: media, + sound: sound, + ), reply: (caption, reply, createdAt, langs, labels, media) => ReplyRecord( caption: caption, reply: reply, @@ -773,8 +831,17 @@ extension BskyRecordAdapter on Record { labels: labels, tags: tags, ), - profile: (displayName, description, avatar, banner, selfLabels, joinedViaStarterPack, pinnedPost, createdAt) => - ProfileRecord( + profile: + ( + displayName, + description, + avatar, + banner, + selfLabels, + joinedViaStarterPack, + pinnedPost, + createdAt, + ) => ProfileRecord( displayName: displayName, description: description, avatar: avatar, @@ -792,16 +859,18 @@ extension BskyRecordAdapter on Record { details: details, labels: labels, ), - bskyPost: (createdAt, text, facets, reply, langs, tags, selfLabels, embed) => BskyPostRecord( - createdAt: createdAt, - text: text, - facets: facets, - reply: reply, - langs: langs, - tags: tags, - selfLabels: selfLabels, - embed: embed, - ).toSparkRecord(), + bskyPost: + (createdAt, text, facets, reply, langs, tags, selfLabels, embed) => + BskyPostRecord( + createdAt: createdAt, + text: text, + facets: facets, + reply: reply, + langs: langs, + tags: tags, + selfLabels: selfLabels, + embed: embed, + ).toSparkRecord(), ); } } diff --git a/lib/src/core/network/atproto/data/adapters/bsky/repo_adapter.dart b/lib/src/core/network/atproto/data/adapters/bsky/repo_adapter.dart index 50c99542..4fbbcb44 100644 --- a/lib/src/core/network/atproto/data/adapters/bsky/repo_adapter.dart +++ b/lib/src/core/network/atproto/data/adapters/bsky/repo_adapter.dart @@ -15,9 +15,9 @@ typedef DeleteRecordCallback = class BskyRepoAdapter { const BskyRepoAdapter(); - // ============================================================================ + // =========================================================================== // Bluesky Cross-Post Handling - // ============================================================================ + // =========================================================================== /// Build a Bluesky counterpart URI from a Spark post URI /// @@ -33,7 +33,8 @@ class BskyRepoAdapter { /// This is a best-effort operation - errors are caught and returned as false. /// Returns true if deletion was successful, false otherwise. /// - /// [deleteRecord] is a callback that performs the actual deletion (typically atproto.repo.deleteRecord) + /// [deleteRecord] is a callback that performs the actual deletion + /// (typically com.atproto.repo.deleteRecord) Future deleteBlueskyCounterpart( DeleteRecordCallback deleteRecord, AtUri sparkUri, @@ -47,7 +48,7 @@ class BskyRepoAdapter { ); return true; } catch (e) { - // Ignore errors like 404 – it simply means the counterpart does not exist. + // Ignore errors like 404, it just means the counterpart doesn't exist. return false; } } diff --git a/lib/src/core/network/atproto/data/models/actor_models.dart b/lib/src/core/network/atproto/data/models/actor_models.dart index 694a04bc..11c1bcf6 100644 --- a/lib/src/core/network/atproto/data/models/actor_models.dart +++ b/lib/src/core/network/atproto/data/models/actor_models.dart @@ -2,7 +2,7 @@ import 'package:atproto/com_atproto_label_defs.dart'; import 'package:atproto/com_atproto_repo_strongref.dart'; import 'package:atproto_core/atproto_core.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:sparksocial/src/core/utils/uri_converter.dart'; +import 'package:spark/src/core/utils/uri_converter.dart'; part 'actor_models.freezed.dart'; part 'actor_models.g.dart'; @@ -22,7 +22,8 @@ abstract class ActorViewer with _$ActorViewer { }) = _ActorViewer; const ActorViewer._(); - factory ActorViewer.fromJson(Map json) => _$ActorViewerFromJson(json); + factory ActorViewer.fromJson(Map json) => + _$ActorViewerFromJson(json); } @freezed @@ -35,10 +36,14 @@ abstract class KnownFollowers with _$KnownFollowers { const KnownFollowers._(); factory KnownFollowers.fromJson(Map json) => switch (json) { - {'followers': final List profiles, 'count': final int count} => _$KnownFollowersFromJson({ - 'count': count, - 'followersDids': profiles.map((e) => e.did).toList(), - }), + { + 'followers': final List profiles, + 'count': final int count, + } => + _$KnownFollowersFromJson({ + 'count': count, + 'followersDids': profiles.map((e) => e.did).toList(), + }), _ => _$KnownFollowersFromJson(json), }; } @@ -51,13 +56,13 @@ abstract class ProfileViewBasic with _$ProfileViewBasic { required String handle, String? displayName, @UriConverter() Uri? avatar, - // associated: lists, feedgens, starterpacks, labelers, chat?? not needed for now ActorViewer? viewer, List? stories, }) = _ProfileViewBasic; const ProfileViewBasic._(); - factory ProfileViewBasic.fromJson(Map json) => _$ProfileViewBasicFromJson(json); + factory ProfileViewBasic.fromJson(Map json) => + _$ProfileViewBasicFromJson(json); } @freezed @@ -69,15 +74,15 @@ abstract class ProfileView with _$ProfileView { String? displayName, String? description, @UriConverter() Uri? avatar, - // associated: lists, feedgens, starterpacks, labelers, chat?? not needed for now - // indexedAt and createdAt + // indexedAt, createdAt ActorViewer? viewer, List