mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: update PostCardFooter with larger tap targets and trailing meta layout

+185 -51
+44 -6
ios/Podfile.lock
··· 3 3 - Flutter 4 4 - Firebase/CoreOnly (12.12.0): 5 5 - FirebaseCore (~> 12.12.0) 6 + - Firebase/Crashlytics (12.12.0): 7 + - Firebase/CoreOnly 8 + - FirebaseCrashlytics (~> 12.12.0) 6 9 - Firebase/Messaging (12.12.0): 7 10 - Firebase/CoreOnly 8 11 - FirebaseMessaging (~> 12.12.0) 9 12 - firebase_core (4.7.0): 10 13 - Firebase/CoreOnly (= 12.12.0) 14 + - Flutter 15 + - firebase_crashlytics (5.2.0): 16 + - Firebase/Crashlytics (= 12.12.0) 17 + - firebase_core 11 18 - Flutter 12 19 - firebase_messaging (16.2.0): 13 20 - Firebase/Messaging (= 12.12.0) ··· 17 24 - FirebaseCoreInternal (~> 12.12.0) 18 25 - GoogleUtilities/Environment (~> 8.1) 19 26 - GoogleUtilities/Logger (~> 8.1) 27 + - FirebaseCoreExtension (12.12.0): 28 + - FirebaseCore (~> 12.12.0) 20 29 - FirebaseCoreInternal (12.12.0): 21 30 - "GoogleUtilities/NSData+zlib (~> 8.1)" 31 + - FirebaseCrashlytics (12.12.1): 32 + - FirebaseCore (~> 12.12.0) 33 + - FirebaseInstallations (~> 12.12.0) 34 + - FirebaseRemoteConfigInterop (~> 12.12.0) 35 + - FirebaseSessions (~> 12.12.0) 36 + - GoogleDataTransport (~> 10.1) 37 + - GoogleUtilities/Environment (~> 8.1) 38 + - nanopb (~> 3.30910.0) 39 + - PromisesObjC (~> 2.4) 22 40 - FirebaseInstallations (12.12.0): 23 41 - FirebaseCore (~> 12.12.0) 24 42 - GoogleUtilities/Environment (~> 8.1) ··· 33 51 - GoogleUtilities/Reachability (~> 8.1) 34 52 - GoogleUtilities/UserDefaults (~> 8.1) 35 53 - nanopb (~> 3.30910.0) 54 + - FirebaseRemoteConfigInterop (12.12.0) 55 + - FirebaseSessions (12.12.0): 56 + - FirebaseCore (~> 12.12.0) 57 + - FirebaseCoreExtension (~> 12.12.0) 58 + - FirebaseInstallations (~> 12.12.0) 59 + - GoogleDataTransport (~> 10.1) 60 + - GoogleUtilities/Environment (~> 8.1) 61 + - GoogleUtilities/UserDefaults (~> 8.1) 62 + - nanopb (~> 3.30910.0) 63 + - PromisesSwift (~> 2.1) 36 64 - Flutter (1.0.0) 37 65 - flutter_local_notifications (0.0.1): 38 - - Flutter 39 - - flutter_native_splash (2.4.3): 40 66 - Flutter 41 67 - gal (1.0.0): 42 68 - Flutter ··· 84 110 - permission_handler_apple (9.3.0): 85 111 - Flutter 86 112 - PromisesObjC (2.4.0) 113 + - PromisesSwift (2.4.0): 114 + - PromisesObjC (= 2.4.0) 87 115 - share_plus (0.0.1): 88 116 - Flutter 89 117 - sqflite_darwin (0.0.4): ··· 149 177 DEPENDENCIES: 150 178 - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) 151 179 - firebase_core (from `.symlinks/plugins/firebase_core/ios`) 180 + - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) 152 181 - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) 153 182 - Flutter (from `Flutter`) 154 183 - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) 155 - - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) 156 184 - gal (from `.symlinks/plugins/gal/darwin`) 157 185 - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) 158 186 - objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`) ··· 171 199 trunk: 172 200 - Firebase 173 201 - FirebaseCore 202 + - FirebaseCoreExtension 174 203 - FirebaseCoreInternal 204 + - FirebaseCrashlytics 175 205 - FirebaseInstallations 176 206 - FirebaseMessaging 207 + - FirebaseRemoteConfigInterop 208 + - FirebaseSessions 177 209 - GoogleDataTransport 178 210 - GoogleUtilities 179 211 - nanopb 180 212 - ObjectBox 181 213 - PromisesObjC 214 + - PromisesSwift 182 215 - sqlite3 183 216 - TensorFlowLiteC 184 217 - TensorFlowLiteSwift ··· 188 221 :path: ".symlinks/plugins/connectivity_plus/ios" 189 222 firebase_core: 190 223 :path: ".symlinks/plugins/firebase_core/ios" 224 + firebase_crashlytics: 225 + :path: ".symlinks/plugins/firebase_crashlytics/ios" 191 226 firebase_messaging: 192 227 :path: ".symlinks/plugins/firebase_messaging/ios" 193 228 Flutter: 194 229 :path: Flutter 195 230 flutter_local_notifications: 196 231 :path: ".symlinks/plugins/flutter_local_notifications/ios" 197 - flutter_native_splash: 198 - :path: ".symlinks/plugins/flutter_native_splash/ios" 199 232 gal: 200 233 :path: ".symlinks/plugins/gal/darwin" 201 234 image_picker_ios: ··· 227 260 connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd 228 261 Firebase: aa154fee4e9b8eac17aa42344988865b3e857d33 229 262 firebase_core: 9156a152117c843440b0b990c785aa0259bc5447 263 + firebase_crashlytics: e24acd48861c5edf6e0f6c134d6a0b28593c76d7 230 264 firebase_messaging: 0d962ab44ff24ed36deb8fa2ee043c4671858269 231 265 FirebaseCore: 86241206e656f5c80c995e370e6c975913b9b284 266 + FirebaseCoreExtension: ff6fd42eb5287e71d3e160450de6509733d9ead7 232 267 FirebaseCoreInternal: 7c12fc3011d889085e765e317d7b9fd1cef97af9 268 + FirebaseCrashlytics: 03f4e20d0c9b7fd6338cb9066f4bfb69d3f42fd0 233 269 FirebaseInstallations: 4e6e162aa4abaaeeeb01dd00179dfc5ad9c2194e 234 270 FirebaseMessaging: 341004946fa7ffc741344b20f1b667514fc93e31 271 + FirebaseRemoteConfigInterop: 23996ab7397494722df4fdd1fd398024389d5da8 272 + FirebaseSessions: 804bd321f2d2f2ddafe74ef7856062aa19f179c2 235 273 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 236 274 flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb 237 - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf 238 275 gal: baecd024ebfd13c441269ca7404792a7152fde89 239 276 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 240 277 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 ··· 245 282 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 246 283 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d 247 284 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 285 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 248 286 share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a 249 287 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 250 288 sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
+89 -43
lib/features/feed/presentation/widgets/post_card_footer.dart
··· 69 69 const horizontalPadding = 8.0; 70 70 const topPadding = 6.0; 71 71 const bottomPadding = 4.0; 72 - const iconSize = 18.0; 72 + const iconSize = 20.0; 73 73 74 74 return LayoutBuilder( 75 75 builder: (context, constraints) { 76 - final compactLayout = constraints.maxWidth < 220; 76 + final compactLayout = constraints.maxWidth < 240; 77 77 final actionSpacing = compactLayout ? 4.0 : 8.0; 78 - final actionPadding = compactLayout ? 1.5 : 3.0; 78 + final actionHorizontalPadding = compactLayout ? 6.0 : 8.0; 79 + final actionVerticalPadding = compactLayout ? 6.0 : 8.0; 80 + final minimumTapTarget = compactLayout ? 40.0 : 44.0; 79 81 final canShowCounts = showCounts && constraints.maxWidth >= 240; 80 - final actions = [ 82 + final actions = <Widget>[ 81 83 _FooterAction( 82 84 icon: Icons.chat_bubble_outline, 83 85 activeIcon: Icons.chat_bubble, ··· 87 89 onTap: isOffline ? null : onReply, 88 90 color: colorScheme.onSurfaceVariant, 89 91 iconSize: iconSize, 90 - padding: actionPadding, 92 + horizontalPadding: actionHorizontalPadding, 93 + verticalPadding: actionVerticalPadding, 94 + minTapTarget: minimumTapTarget, 91 95 showCount: canShowCounts, 92 96 tooltip: isOffline ? offlineActionMessage('reply to this post') : null, 93 97 ), ··· 102 106 color: colorScheme.onSurfaceVariant, 103 107 activeColor: Colors.green, 104 108 iconSize: iconSize, 105 - padding: actionPadding, 109 + horizontalPadding: actionHorizontalPadding, 110 + verticalPadding: actionVerticalPadding, 111 + minTapTarget: minimumTapTarget, 106 112 showCount: canShowCounts, 107 113 tooltip: isOffline ? offlineActionMessage('repost this post') : null, 108 114 ), ··· 116 122 color: colorScheme.onSurfaceVariant, 117 123 activeColor: Colors.pink, 118 124 iconSize: iconSize, 119 - padding: actionPadding, 125 + horizontalPadding: actionHorizontalPadding, 126 + verticalPadding: actionVerticalPadding, 127 + minTapTarget: minimumTapTarget, 120 128 showCount: canShowCounts, 121 129 tooltip: isOffline ? offlineActionMessage('like this post') : null, 122 130 ), ··· 131 139 color: colorScheme.onSurfaceVariant, 132 140 activeColor: saveActiveColor, 133 141 iconSize: iconSize, 134 - padding: actionPadding, 142 + horizontalPadding: actionHorizontalPadding, 143 + verticalPadding: actionVerticalPadding, 144 + minTapTarget: minimumTapTarget, 135 145 showCount: canShowCounts, 136 146 ), 137 - if (onMore != null) 138 - _FooterAction( 139 - icon: Icons.more_vert, 140 - activeIcon: Icons.more_vert, 141 - isActive: false, 142 - isLoading: false, 143 - count: 0, 144 - onTap: onMore, 145 - color: colorScheme.onSurfaceVariant, 146 - iconSize: iconSize, 147 - padding: actionPadding, 148 - showCount: false, 149 - ), 150 147 ]; 148 + final trailingMeta = _buildTrailingMeta( 149 + context: context, 150 + colorScheme: colorScheme, 151 + iconSize: iconSize, 152 + actionHorizontalPadding: actionHorizontalPadding, 153 + actionVerticalPadding: actionVerticalPadding, 154 + minimumTapTarget: minimumTapTarget, 155 + ); 151 156 152 157 return Container( 153 158 decoration: BoxDecoration( ··· 165 170 children: actions, 166 171 ), 167 172 const SizedBox(height: 4), 168 - Align(alignment: Alignment.centerRight, child: _buildTimestamp(context, colorScheme)), 173 + Align(alignment: Alignment.centerRight, child: trailingMeta), 169 174 ], 170 175 ) 171 176 : Row( ··· 173 178 for (int i = 0; i < actions.length; i++) ...[if (i > 0) SizedBox(width: actionSpacing), actions[i]], 174 179 SizedBox(width: actionSpacing), 175 180 Expanded( 176 - child: Align(alignment: Alignment.centerRight, child: _buildTimestamp(context, colorScheme)), 181 + child: Align(alignment: Alignment.centerRight, child: trailingMeta), 177 182 ), 178 183 ], 179 184 ), ··· 218 223 ); 219 224 } 220 225 226 + Widget _buildTrailingMeta({ 227 + required BuildContext context, 228 + required ColorScheme colorScheme, 229 + required double iconSize, 230 + required double actionHorizontalPadding, 231 + required double actionVerticalPadding, 232 + required double minimumTapTarget, 233 + }) { 234 + return Row( 235 + key: const ValueKey('post_footer_trailing_meta'), 236 + mainAxisSize: MainAxisSize.min, 237 + children: [ 238 + _buildTimestamp(context, colorScheme), 239 + if (onMore != null) ...[ 240 + const SizedBox(width: 2), 241 + _FooterAction( 242 + icon: Icons.more_vert, 243 + activeIcon: Icons.more_vert, 244 + isActive: false, 245 + isLoading: false, 246 + count: 0, 247 + onTap: onMore, 248 + color: colorScheme.onSurfaceVariant, 249 + iconSize: iconSize, 250 + horizontalPadding: actionHorizontalPadding, 251 + verticalPadding: actionVerticalPadding, 252 + minTapTarget: minimumTapTarget, 253 + showCount: false, 254 + ), 255 + ], 256 + ], 257 + ); 258 + } 259 + 221 260 void _showSaveOptions(BuildContext context) { 222 261 HapticHelper.mediumImpact(); 223 262 final isLocalSaved = isSaved && (saveType == 'local' || saveType == 'both'); ··· 254 293 required this.isActive, 255 294 required this.isLoading, 256 295 required this.iconSize, 257 - required this.padding, 296 + required this.horizontalPadding, 297 + required this.verticalPadding, 298 + required this.minTapTarget, 258 299 required this.count, 259 300 required this.showCount, 260 301 this.onTap, ··· 269 310 final bool isActive; 270 311 final bool isLoading; 271 312 final double iconSize; 272 - final double padding; 313 + final double horizontalPadding; 314 + final double verticalPadding; 315 + final double minTapTarget; 273 316 final int count; 274 317 final bool showCount; 275 318 final VoidCallback? onTap; ··· 286 329 Widget button = InkWell( 287 330 onTap: isLoading ? null : onTap, 288 331 onLongPress: onLongPress, 289 - borderRadius: BorderRadius.zero, 290 - child: Padding( 291 - padding: EdgeInsets.symmetric(horizontal: padding, vertical: padding), 292 - child: Row( 293 - mainAxisSize: MainAxisSize.min, 294 - children: [ 295 - if (isLoading) 296 - SizedBox( 297 - width: iconSize, 298 - height: iconSize, 299 - child: CircularProgressIndicator(strokeWidth: 2, color: iconColor), 300 - ) 301 - else 302 - Icon(isActive ? activeIcon : icon, size: iconSize, color: iconColor), 303 - if (showCount && count > 0) ...[ 304 - const SizedBox(width: 4), 305 - Text(formatCount(count), style: context.textTheme.bodySmall?.copyWith(color: iconColor)), 332 + borderRadius: BorderRadius.circular(8), 333 + child: ConstrainedBox( 334 + constraints: BoxConstraints(minWidth: minTapTarget, minHeight: minTapTarget), 335 + child: Padding( 336 + padding: EdgeInsets.symmetric(horizontal: horizontalPadding, vertical: verticalPadding), 337 + child: Row( 338 + mainAxisSize: MainAxisSize.min, 339 + children: [ 340 + if (isLoading) 341 + SizedBox( 342 + width: iconSize, 343 + height: iconSize, 344 + child: CircularProgressIndicator(strokeWidth: 2, color: iconColor), 345 + ) 346 + else 347 + Icon(isActive ? activeIcon : icon, size: iconSize, color: iconColor), 348 + if (showCount && count > 0) ...[ 349 + const SizedBox(width: 4), 350 + Text(formatCount(count), style: context.textTheme.bodySmall?.copyWith(color: iconColor)), 351 + ], 306 352 ], 307 - ], 353 + ), 308 354 ), 309 355 ), 310 356 );