Compare changes

Choose any two refs to compare.

+42 -5
lib/providers/feed_provider.dart
···
import 'auth_provider.dart';
import 'vote_provider.dart';
+
/// Feed types available in the app
+
enum FeedType {
+
/// All posts across the network
+
discover,
+
+
/// Posts from subscribed communities (authenticated only)
+
forYou,
+
}
+
/// Feed Provider
///
/// Manages feed state and fetching logic.
···
if (kDebugMode) {
debugPrint('๐Ÿ”’ User signed out - clearing feed');
}
+
// Reset feed type to Discover since For You requires auth
+
_feedType = FeedType.discover;
reset();
// Automatically load the public discover feed
loadFeed(refresh: true);
···
// Feed configuration
String _sort = 'hot';
String? _timeframe;
+
FeedType _feedType = FeedType.discover;
// Time update mechanism for periodic UI refreshes
Timer? _timeUpdateTimer;
···
String get sort => _sort;
String? get timeframe => _timeframe;
DateTime? get currentTime => _currentTime;
+
FeedType get feedType => _feedType;
+
+
/// Check if For You feed is available (requires authentication)
+
bool get isForYouAvailable => _authProvider.isAuthenticated;
/// Start periodic time updates for "time ago" strings
///
···
}
}
-
/// Load feed based on authentication state (business logic
-
/// encapsulation)
+
/// Load feed based on current feed type
///
/// This method encapsulates the business logic of deciding which feed
-
/// to fetch. Previously this logic was in the UI layer (FeedScreen),
-
/// violating clean architecture.
+
/// to fetch based on the selected feed type.
Future<void> loadFeed({bool refresh = false}) async {
-
if (_authProvider.isAuthenticated) {
+
// For You requires authentication - fall back to Discover if not
+
if (_feedType == FeedType.forYou && _authProvider.isAuthenticated) {
await fetchTimeline(refresh: refresh);
} else {
await fetchDiscover(refresh: refresh);
···
}
}
+
/// Switch feed type and reload
+
Future<void> setFeedType(FeedType type) async {
+
if (_feedType == type) {
+
return;
+
}
+
+
// For You requires authentication
+
if (type == FeedType.forYou && !_authProvider.isAuthenticated) {
+
return;
+
}
+
+
_feedType = type;
+
// Reset pagination state but keep posts visible until new feed loads
+
_cursor = null;
+
_hasMore = true;
+
_error = null;
+
notifyListeners();
+
+
// Load new feed - old posts stay visible until new ones arrive
+
await loadFeed(refresh: true);
+
}
+
/// Common feed fetching logic (DRY principle - eliminates code
/// duplication)
Future<void> _fetchFeed({
+12 -8
lib/screens/home/search_screen.dart lib/screens/home/communities_screen.dart
···
import '../../constants/app_colors.dart';
-
class SearchScreen extends StatelessWidget {
-
const SearchScreen({super.key});
+
class CommunitiesScreen extends StatelessWidget {
+
const CommunitiesScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
-
backgroundColor: const Color(0xFF0B0F14),
+
backgroundColor: AppColors.background,
appBar: AppBar(
-
backgroundColor: const Color(0xFF0B0F14),
+
backgroundColor: AppColors.background,
foregroundColor: Colors.white,
-
title: const Text('Search'),
+
title: const Text('Communities'),
automaticallyImplyLeading: false,
),
body: const Center(
···
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
-
Icon(Icons.search, size: 64, color: AppColors.primary),
+
Icon(
+
Icons.workspaces_outlined,
+
size: 64,
+
color: AppColors.primary,
+
),
SizedBox(height: 24),
Text(
-
'Search',
+
'Communities',
style: TextStyle(
fontSize: 28,
color: Colors.white,
···
),
SizedBox(height: 16),
Text(
-
'Search communities and conversations',
+
'Discover and join communities',
style: TextStyle(fontSize: 16, color: Color(0xFFB6C2D2)),
textAlign: TextAlign.center,
),
+162 -14
lib/screens/home/feed_screen.dart
···
import '../../models/post.dart';
import '../../providers/auth_provider.dart';
import '../../providers/feed_provider.dart';
+
import '../../widgets/icons/bluesky_icons.dart';
import '../../widgets/post_card.dart';
+
/// Header layout constants
+
const double _kHeaderHeight = 44;
+
const double _kTabUnderlineWidth = 28;
+
const double _kTabUnderlineHeight = 3;
+
const double _kHeaderContentPadding = _kHeaderHeight;
+
class FeedScreen extends StatefulWidget {
-
const FeedScreen({super.key});
+
const FeedScreen({super.key, this.onSearchTap});
+
+
/// Callback when search icon is tapped (to switch to communities tab)
+
final VoidCallback? onSearchTap;
@override
State<FeedScreen> createState() => _FeedScreenState();
···
);
final isLoading = context.select<FeedProvider, bool>((p) => p.isLoading);
final error = context.select<FeedProvider, String?>((p) => p.error);
+
final feedType = context.select<FeedProvider, FeedType>(
+
(p) => p.feedType,
+
);
// IMPORTANT: This relies on FeedProvider creating new list instances
// (_posts = [..._posts, ...response.feed]) rather than mutating in-place.
···
return Scaffold(
backgroundColor: AppColors.background,
-
appBar: AppBar(
-
backgroundColor: AppColors.background,
-
foregroundColor: AppColors.textPrimary,
-
title: Text(isAuthenticated ? 'Feed' : 'Explore'),
-
automaticallyImplyLeading: false,
-
),
body: SafeArea(
-
child: _buildBody(
-
isLoading: isLoading,
-
error: error,
-
posts: posts,
-
isLoadingMore: isLoadingMore,
-
isAuthenticated: isAuthenticated,
-
currentTime: currentTime,
+
child: Stack(
+
children: [
+
// Feed content (behind header)
+
_buildBody(
+
isLoading: isLoading,
+
error: error,
+
posts: posts,
+
isLoadingMore: isLoadingMore,
+
isAuthenticated: isAuthenticated,
+
currentTime: currentTime,
+
),
+
// Transparent header overlay
+
_buildHeader(
+
feedType: feedType,
+
isAuthenticated: isAuthenticated,
+
),
+
],
+
),
+
),
+
);
+
}
+
+
Widget _buildHeader({
+
required FeedType feedType,
+
required bool isAuthenticated,
+
}) {
+
return Container(
+
height: _kHeaderHeight,
+
decoration: BoxDecoration(
+
// Gradient fade from solid to transparent
+
gradient: LinearGradient(
+
begin: Alignment.topCenter,
+
end: Alignment.bottomCenter,
+
colors: [
+
AppColors.background,
+
AppColors.background.withValues(alpha: 0.8),
+
AppColors.background.withValues(alpha: 0),
+
],
+
stops: const [0.0, 0.6, 1.0],
+
),
+
),
+
padding: const EdgeInsets.symmetric(horizontal: 16),
+
child: Row(
+
children: [
+
// Feed type tabs in the center
+
Expanded(
+
child: _buildFeedTypeTabs(
+
feedType: feedType,
+
isAuthenticated: isAuthenticated,
+
),
+
),
+
// Search/Communities icon on the right
+
if (widget.onSearchTap != null)
+
Semantics(
+
label: 'Navigate to Communities',
+
button: true,
+
child: InkWell(
+
onTap: widget.onSearchTap,
+
borderRadius: BorderRadius.circular(20),
+
splashColor: AppColors.primary.withValues(alpha: 0.2),
+
child: Padding(
+
padding: const EdgeInsets.all(8),
+
child: BlueSkyIcon.search(color: AppColors.textPrimary),
+
),
+
),
+
),
+
],
+
),
+
);
+
}
+
+
Widget _buildFeedTypeTabs({
+
required FeedType feedType,
+
required bool isAuthenticated,
+
}) {
+
// If not authenticated, only show Discover
+
if (!isAuthenticated) {
+
return Center(
+
child: _buildFeedTypeTab(
+
label: 'Discover',
+
isActive: true,
+
onTap: null,
+
),
+
);
+
}
+
+
// Authenticated: show both tabs side by side (TikTok style)
+
return Row(
+
mainAxisAlignment: MainAxisAlignment.center,
+
children: [
+
_buildFeedTypeTab(
+
label: 'Discover',
+
isActive: feedType == FeedType.discover,
+
onTap: () => _switchToFeedType(FeedType.discover),
+
),
+
const SizedBox(width: 24),
+
_buildFeedTypeTab(
+
label: 'For You',
+
isActive: feedType == FeedType.forYou,
+
onTap: () => _switchToFeedType(FeedType.forYou),
+
),
+
],
+
);
+
}
+
+
Widget _buildFeedTypeTab({
+
required String label,
+
required bool isActive,
+
required VoidCallback? onTap,
+
}) {
+
return Semantics(
+
label: '$label feed${isActive ? ', selected' : ''}',
+
button: true,
+
selected: isActive,
+
child: GestureDetector(
+
onTap: onTap,
+
behavior: HitTestBehavior.opaque,
+
child: Column(
+
mainAxisSize: MainAxisSize.min,
+
mainAxisAlignment: MainAxisAlignment.center,
+
children: [
+
Text(
+
label,
+
style: TextStyle(
+
color: isActive
+
? AppColors.textPrimary
+
: AppColors.textSecondary.withValues(alpha: 0.6),
+
fontSize: 16,
+
fontWeight: isActive ? FontWeight.w700 : FontWeight.w400,
+
),
+
),
+
const SizedBox(height: 2),
+
// Underline indicator (TikTok style)
+
Container(
+
width: _kTabUnderlineWidth,
+
height: _kTabUnderlineHeight,
+
decoration: BoxDecoration(
+
color: isActive ? AppColors.textPrimary : Colors.transparent,
+
borderRadius: BorderRadius.circular(2),
+
),
+
),
+
],
),
),
);
}
+
void _switchToFeedType(FeedType type) {
+
Provider.of<FeedProvider>(context, listen: false).setFeedType(type);
+
}
+
Widget _buildBody({
required bool isLoading,
required String? error,
···
color: AppColors.primary,
child: ListView.builder(
controller: _scrollController,
+
// Add top padding so content isn't hidden behind transparent header
+
padding: const EdgeInsets.only(top: _kHeaderContentPadding),
// Add extra item for loading indicator or pagination error
itemCount: posts.length + (isLoadingMore || error != null ? 1 : 0),
itemBuilder: (context, index) {
+24 -13
lib/screens/home/main_shell_screen.dart
···
import '../../constants/app_colors.dart';
import '../../widgets/icons/bluesky_icons.dart';
+
import 'communities_screen.dart';
import 'create_post_screen.dart';
import 'feed_screen.dart';
import 'notifications_screen.dart';
import 'profile_screen.dart';
-
import 'search_screen.dart';
class MainShellScreen extends StatefulWidget {
const MainShellScreen({super.key});
···
class _MainShellScreenState extends State<MainShellScreen> {
int _selectedIndex = 0;
-
static const List<Widget> _screens = [
-
FeedScreen(),
-
SearchScreen(),
-
CreatePostScreen(),
-
NotificationsScreen(),
-
ProfileScreen(),
-
];
-
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
+
void _onCommunitiesTap() {
+
setState(() {
+
_selectedIndex = 1; // Switch to communities tab
+
});
+
}
+
@override
Widget build(BuildContext context) {
return Scaffold(
-
body: _screens[_selectedIndex],
+
body: IndexedStack(
+
index: _selectedIndex,
+
children: [
+
FeedScreen(onSearchTap: _onCommunitiesTap),
+
const CommunitiesScreen(),
+
const CreatePostScreen(),
+
const NotificationsScreen(),
+
const ProfileScreen(),
+
],
+
),
bottomNavigationBar: Container(
decoration: const BoxDecoration(
color: Color(0xFF0B0F14),
···
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(0, 'home', 'Home'),
-
_buildNavItem(1, 'search', 'Search'),
+
_buildNavItem(1, 'communities', 'Communities'),
_buildNavItem(2, 'plus', 'Create'),
_buildNavItem(3, 'bell', 'Notifications'),
_buildNavItem(4, 'person', 'Me'),
···
case 'home':
icon = BlueSkyIcon.homeSimple(color: color);
break;
-
case 'search':
-
icon = BlueSkyIcon.search(color: color);
+
case 'communities':
+
icon = Icon(
+
isSelected ? Icons.workspaces : Icons.workspaces_outlined,
+
color: color,
+
size: 24,
+
);
break;
case 'plus':
icon = BlueSkyIcon.plus(color: color);
+21
lib/providers/comments_provider.dart
···
String? _cursor;
bool _hasMore = true;
+
// Collapsed thread state - stores URIs of collapsed comments
+
final Set<String> _collapsedComments = {};
+
// Current post being viewed
String? _postUri;
String? _postCid;
···
String get sort => _sort;
String? get timeframe => _timeframe;
ValueNotifier<DateTime?> get currentTimeNotifier => _currentTimeNotifier;
+
Set<String> get collapsedComments => _collapsedComments;
+
+
/// Toggle collapsed state for a comment thread
+
///
+
/// When collapsed, the comment's replies are hidden from view.
+
/// Long-pressing the same comment again will expand the thread.
+
void toggleCollapsed(String uri) {
+
if (_collapsedComments.contains(uri)) {
+
_collapsedComments.remove(uri);
+
} else {
+
_collapsedComments.add(uri);
+
}
+
notifyListeners();
+
}
+
+
/// Check if a specific comment is collapsed
+
bool isCollapsed(String uri) => _collapsedComments.contains(uri);
/// Start periodic time updates for "time ago" strings
///
···
_postUri = null;
_postCid = null;
_pendingRefresh = false;
+
_collapsedComments.clear();
notifyListeners();
}
+8
lib/screens/home/post_detail_screen.dart
···
currentTimeNotifier:
commentsProvider.currentTimeNotifier,
onCommentTap: _openReplyToComment,
+
collapsedComments: commentsProvider.collapsedComments,
+
onCollapseToggle: commentsProvider.toggleCollapsed,
);
},
childCount:
···
required this.comment,
required this.currentTimeNotifier,
this.onCommentTap,
+
this.collapsedComments = const {},
+
this.onCollapseToggle,
});
final ThreadViewComment comment;
final ValueNotifier<DateTime?> currentTimeNotifier;
final void Function(ThreadViewComment)? onCommentTap;
+
final Set<String> collapsedComments;
+
final void Function(String uri)? onCollapseToggle;
@override
Widget build(BuildContext context) {
···
currentTime: currentTime,
maxDepth: 6,
onCommentTap: onCommentTap,
+
collapsedComments: collapsedComments,
+
onCollapseToggle: onCollapseToggle,
);
},
);
+117 -67
lib/widgets/comment_card.dart
···
/// - Heart vote button with optimistic updates via VoteProvider
/// - Visual threading indicator based on nesting depth
/// - Tap-to-reply functionality via [onTap] callback
+
/// - Long-press to collapse thread via [onLongPress] callback
///
/// The [currentTime] parameter allows passing the current time for
/// time-ago calculations, enabling periodic updates and testing.
+
///
+
/// When [isCollapsed] is true, displays a badge showing [collapsedCount]
+
/// hidden replies on the threading indicator bar.
class CommentCard extends StatelessWidget {
const CommentCard({
required this.comment,
this.depth = 0,
this.currentTime,
this.onTap,
+
this.onLongPress,
+
this.isCollapsed = false,
+
this.collapsedCount = 0,
super.key,
});
···
/// Callback when the comment is tapped (for reply functionality)
final VoidCallback? onTap;
+
/// Callback when the comment is long-pressed (for collapse functionality)
+
final VoidCallback? onLongPress;
+
+
/// Whether this comment's thread is currently collapsed
+
final bool isCollapsed;
+
+
/// Number of replies hidden when collapsed
+
final int collapsedCount;
+
@override
Widget build(BuildContext context) {
// All comments get at least 1 threading line (depth + 1)
···
// the stroke width)
final borderLeftOffset = (threadingLineCount * 6.0) + 2.0;
-
return InkWell(
-
onTap: onTap,
-
child: Container(
-
decoration: const BoxDecoration(color: AppColors.background),
-
child: Stack(
-
children: [
-
// Threading indicators - vertical lines showing nesting ancestry
-
Positioned.fill(
-
child: CustomPaint(
-
painter: _CommentDepthPainter(depth: threadingLineCount),
+
return GestureDetector(
+
onLongPress: onLongPress != null
+
? () {
+
HapticFeedback.mediumImpact();
+
onLongPress!();
+
}
+
: null,
+
child: InkWell(
+
onTap: onTap,
+
child: Container(
+
decoration: const BoxDecoration(color: AppColors.background),
+
child: Stack(
+
children: [
+
// Threading indicators - vertical lines showing nesting ancestry
+
Positioned.fill(
+
child: CustomPaint(
+
painter: _CommentDepthPainter(depth: threadingLineCount),
+
),
),
-
),
-
// Bottom border (starts after threading lines, not overlapping them)
-
Positioned(
-
left: borderLeftOffset,
-
right: 0,
-
bottom: 0,
-
child: Container(height: 1, color: AppColors.border),
-
),
-
// Comment content with depth-based left padding
-
Padding(
-
padding: EdgeInsets.fromLTRB(leftPadding, 12, 16, 8),
-
child: Column(
-
crossAxisAlignment: CrossAxisAlignment.start,
-
children: [
-
// Author info row
-
Row(
-
children: [
-
// Author avatar
-
_buildAuthorAvatar(comment.author),
-
const SizedBox(width: 8),
-
Expanded(
-
child: Column(
-
crossAxisAlignment: CrossAxisAlignment.start,
-
children: [
-
// Author handle
-
Text(
-
'@${comment.author.handle}',
-
style: TextStyle(
-
color: AppColors.textPrimary.withValues(
-
alpha: 0.5,
+
// Collapsed count badge - positioned after threading lines
+
// to avoid overlap at any depth level
+
if (isCollapsed && collapsedCount > 0)
+
Positioned(
+
left: borderLeftOffset + 4,
+
bottom: 8,
+
child: Container(
+
padding: const EdgeInsets.symmetric(
+
horizontal: 6,
+
vertical: 2,
+
),
+
decoration: BoxDecoration(
+
color: AppColors.primary,
+
borderRadius: BorderRadius.circular(8),
+
),
+
child: Text(
+
'+$collapsedCount hidden',
+
style: const TextStyle(
+
color: AppColors.textPrimary,
+
fontSize: 10,
+
fontWeight: FontWeight.w500,
+
),
+
),
+
),
+
),
+
// Bottom border
+
// (starts after threading lines, not overlapping them)
+
Positioned(
+
left: borderLeftOffset,
+
right: 0,
+
bottom: 0,
+
child: Container(height: 1, color: AppColors.border),
+
),
+
// Comment content with depth-based left padding
+
Padding(
+
padding: EdgeInsets.fromLTRB(leftPadding, 12, 16, 8),
+
child: Column(
+
crossAxisAlignment: CrossAxisAlignment.start,
+
children: [
+
// Author info row
+
Row(
+
children: [
+
// Author avatar
+
_buildAuthorAvatar(comment.author),
+
const SizedBox(width: 8),
+
Expanded(
+
child: Column(
+
crossAxisAlignment: CrossAxisAlignment.start,
+
children: [
+
// Author handle
+
Text(
+
'@${comment.author.handle}',
+
style: TextStyle(
+
color: AppColors.textPrimary.withValues(
+
alpha: 0.5,
+
),
+
fontSize: 13,
+
fontWeight: FontWeight.w500,
),
-
fontSize: 13,
-
fontWeight: FontWeight.w500,
),
-
),
-
],
-
),
-
),
-
// Time ago
-
Text(
-
DateTimeUtils.formatTimeAgo(
-
comment.createdAt,
-
currentTime: currentTime,
+
],
+
),
),
-
style: TextStyle(
-
color: AppColors.textPrimary.withValues(alpha: 0.5),
-
fontSize: 12,
+
// Time ago
+
Text(
+
DateTimeUtils.formatTimeAgo(
+
comment.createdAt,
+
currentTime: currentTime,
+
),
+
style: TextStyle(
+
color: AppColors.textPrimary.withValues(alpha: 0.5),
+
fontSize: 12,
+
),
),
-
),
+
],
+
),
+
const SizedBox(height: 8),
+
+
// Comment content
+
if (comment.content.isNotEmpty) ...[
+
_buildCommentContent(comment),
+
const SizedBox(height: 8),
],
-
),
-
const SizedBox(height: 8),
-
// Comment content
-
if (comment.content.isNotEmpty) ...[
-
_buildCommentContent(comment),
-
const SizedBox(height: 8),
+
// Action buttons (just vote for now)
+
_buildActionButtons(context),
],
-
-
// Action buttons (just vote for now)
-
_buildActionButtons(context),
-
],
+
),
),
-
),
-
],
+
],
+
),
),
),
);
+77 -14
lib/widgets/comment_thread.dart
···
/// - Limits nesting depth to prevent excessive indentation
/// - Shows "Load more replies" button when hasMore is true
/// - Supports tap-to-reply via [onCommentTap] callback
+
/// - Supports long-press to collapse threads via [onCollapseToggle] callback
///
/// The [maxDepth] parameter controls how deeply nested comments can be
/// before they're rendered at the same level to prevent UI overflow.
+
///
+
/// When a comment is collapsed (via [collapsedComments]), its replies are
+
/// hidden with a smooth animation and a badge shows the hidden count.
class CommentThread extends StatelessWidget {
const CommentThread({
required this.thread,
···
this.currentTime,
this.onLoadMoreReplies,
this.onCommentTap,
+
this.collapsedComments = const {},
+
this.onCollapseToggle,
super.key,
});
···
/// Callback when a comment is tapped (for reply functionality)
final void Function(ThreadViewComment)? onCommentTap;
+
/// Set of collapsed comment URIs
+
final Set<String> collapsedComments;
+
+
/// Callback when a comment collapse state is toggled
+
final void Function(String uri)? onCollapseToggle;
+
+
/// Count all descendants recursively
+
static int countDescendants(ThreadViewComment thread) {
+
if (thread.replies == null || thread.replies!.isEmpty) {
+
return 0;
+
}
+
var count = thread.replies!.length;
+
for (final reply in thread.replies!) {
+
count += countDescendants(reply);
+
}
+
return count;
+
}
+
@override
Widget build(BuildContext context) {
// Calculate effective depth (flatten after maxDepth)
final effectiveDepth = depth > maxDepth ? maxDepth : depth;
+
// Check if this comment is collapsed
+
final isCollapsed = collapsedComments.contains(thread.comment.uri);
+
final collapsedCount = isCollapsed ? countDescendants(thread) : 0;
+
+
// Check if there are replies to render
+
final hasReplies = thread.replies != null && thread.replies!.isNotEmpty;
+
+
// Only build replies widget when NOT collapsed (optimization)
+
// When collapsed, AnimatedSwitcher shows SizedBox.shrink() so children
+
// are never mounted - no need to build them at all
+
final repliesWidget = hasReplies && !isCollapsed
+
? Column(
+
key: const ValueKey('replies'),
+
crossAxisAlignment: CrossAxisAlignment.start,
+
children: thread.replies!.map((reply) {
+
return CommentThread(
+
thread: reply,
+
depth: depth + 1,
+
maxDepth: maxDepth,
+
currentTime: currentTime,
+
onLoadMoreReplies: onLoadMoreReplies,
+
onCommentTap: onCommentTap,
+
collapsedComments: collapsedComments,
+
onCollapseToggle: onCollapseToggle,
+
);
+
}).toList(),
+
)
+
: null;
+
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
-
// Render the comment with tap handler
+
// Render the comment with tap and long-press handlers
CommentCard(
comment: thread.comment,
depth: effectiveDepth,
currentTime: currentTime,
onTap: onCommentTap != null ? () => onCommentTap!(thread) : null,
+
onLongPress: onCollapseToggle != null
+
? () => onCollapseToggle!(thread.comment.uri)
+
: null,
+
isCollapsed: isCollapsed,
+
collapsedCount: collapsedCount,
),
-
// Render replies recursively
-
if (thread.replies != null && thread.replies!.isNotEmpty)
-
...thread.replies!.map(
-
(reply) => CommentThread(
-
thread: reply,
-
depth: depth + 1,
-
maxDepth: maxDepth,
-
currentTime: currentTime,
-
onLoadMoreReplies: onLoadMoreReplies,
-
onCommentTap: onCommentTap,
-
),
+
// Render replies with animation
+
if (hasReplies)
+
AnimatedSwitcher(
+
duration: const Duration(milliseconds: 200),
+
switchInCurve: Curves.easeInOutCubicEmphasized,
+
switchOutCurve: Curves.easeInOutCubicEmphasized,
+
transitionBuilder: (Widget child, Animation<double> animation) {
+
return SizeTransition(
+
sizeFactor: animation,
+
axisAlignment: -1,
+
child: child,
+
);
+
},
+
child: isCollapsed
+
? const SizedBox.shrink(key: ValueKey('collapsed'))
+
: repliesWidget,
),
-
// Show "Load more replies" button if there are more
-
if (thread.hasMore) _buildLoadMoreButton(context),
+
// Show "Load more replies" button if there are more (and not collapsed)
+
if (thread.hasMore && !isCollapsed) _buildLoadMoreButton(context),
],
);
}
+3
.gitignore
···
/android/app/debug
/android/app/profile
/android/app/release
+
+
# macOS (not targeting this platform)
+
macos/
+1
ios/Flutter/Debug.xcconfig
···
+
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
+1
ios/Flutter/Release.xcconfig
···
+
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
+43
ios/Podfile
···
+
# Uncomment this line to define a global platform for your project
+
# platform :ios, '13.0'
+
+
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+
project 'Runner', {
+
'Debug' => :debug,
+
'Profile' => :release,
+
'Release' => :release,
+
}
+
+
def flutter_root
+
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+
unless File.exist?(generated_xcode_build_settings_path)
+
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+
end
+
+
File.foreach(generated_xcode_build_settings_path) do |line|
+
matches = line.match(/FLUTTER_ROOT\=(.*)/)
+
return matches[1].strip if matches
+
end
+
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+
end
+
+
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+
flutter_ios_podfile_setup
+
+
target 'Runner' do
+
use_frameworks!
+
+
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+
target 'RunnerTests' do
+
inherit! :search_paths
+
end
+
end
+
+
post_install do |installer|
+
installer.pods_project.targets.each do |target|
+
flutter_additional_ios_build_settings(target)
+
end
+
end
+4 -4
pubspec.lock
···
dependency: transitive
description:
name: meta
-
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
-
version: "1.16.0"
+
version: "1.17.0"
mime:
dependency: transitive
description:
···
dependency: transitive
description:
name: test_api
-
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
+
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
-
version: "0.7.6"
+
version: "0.7.7"
typed_data:
dependency: transitive
description:
+1 -1
ios/Flutter/AppFrameworkInfo.plist
···
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
-
<string>12.0</string>
+
<string>13.0</string>
</dict>
</plist>
+68
ios/Podfile.lock
···
+
PODS:
+
- Flutter (1.0.0)
+
- flutter_secure_storage (6.0.0):
+
- Flutter
+
- flutter_web_auth_2 (3.0.0):
+
- Flutter
+
- path_provider_foundation (0.0.1):
+
- Flutter
+
- FlutterMacOS
+
- share_plus (0.0.1):
+
- Flutter
+
- shared_preferences_foundation (0.0.1):
+
- Flutter
+
- FlutterMacOS
+
- sqflite_darwin (0.0.4):
+
- Flutter
+
- FlutterMacOS
+
- url_launcher_ios (0.0.1):
+
- Flutter
+
- video_player_avfoundation (0.0.1):
+
- Flutter
+
- FlutterMacOS
+
+
DEPENDENCIES:
+
- Flutter (from `Flutter`)
+
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
+
- flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
+
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
+
- share_plus (from `.symlinks/plugins/share_plus/ios`)
+
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
+
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
+
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
+
+
EXTERNAL SOURCES:
+
Flutter:
+
:path: Flutter
+
flutter_secure_storage:
+
:path: ".symlinks/plugins/flutter_secure_storage/ios"
+
flutter_web_auth_2:
+
:path: ".symlinks/plugins/flutter_web_auth_2/ios"
+
path_provider_foundation:
+
:path: ".symlinks/plugins/path_provider_foundation/darwin"
+
share_plus:
+
:path: ".symlinks/plugins/share_plus/ios"
+
shared_preferences_foundation:
+
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+
sqflite_darwin:
+
:path: ".symlinks/plugins/sqflite_darwin/darwin"
+
url_launcher_ios:
+
:path: ".symlinks/plugins/url_launcher_ios/ios"
+
video_player_avfoundation:
+
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
+
+
SPEC CHECKSUMS:
+
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
+
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
+
flutter_web_auth_2: 5c8d9dcd7848b5a9efb086d24e7a9adcae979c80
+
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
+
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
+
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
+
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
+
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
+
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
+
+
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
+
+
COCOAPODS: 1.16.2
+115 -3
ios/Runner.xcodeproj/project.pbxproj
···
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+
16E67738C4AF07C35AA47470 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82EC9CF23352AC72F2003AAD /* Pods_RunnerTests.framework */; };
+
2220618238061C279E522B7E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2A2DB00FCDBEA05F362717D /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
···
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+
24C909BB605D55AC18D4D709 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+
58C1A39422F3ADDA7073882C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
+
62C533E7959427EBD54BF4E0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
+
7404320A2A2665D2993CC4A9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+
82EC9CF23352AC72F2003AAD /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+
91248B6140D65FC329BE4089 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
···
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+
CAFF337A6DF135B15E2E5A82 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
+
D2A2DB00FCDBEA05F362717D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F2B3C8D12D0C8A5E00ABCDEF /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+
6A654D0E96DDFAB5016AAB44 /* Frameworks */ = {
+
isa = PBXFrameworksBuildPhase;
+
buildActionMask = 2147483647;
+
files = (
+
16E67738C4AF07C35AA47470 /* Pods_RunnerTests.framework in Frameworks */,
+
);
+
runOnlyForDeploymentPostprocessing = 0;
+
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+
2220618238061C279E522B7E /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
···
path = RunnerTests;
sourceTree = "<group>";
};
+
86A9EDA55647EB05647C404F /* Frameworks */ = {
+
isa = PBXGroup;
+
children = (
+
D2A2DB00FCDBEA05F362717D /* Pods_Runner.framework */,
+
82EC9CF23352AC72F2003AAD /* Pods_RunnerTests.framework */,
+
);
+
name = Frameworks;
+
sourceTree = "<group>";
+
};
+
8AC347B174FB51D9D1783044 /* Pods */ = {
+
isa = PBXGroup;
+
children = (
+
91248B6140D65FC329BE4089 /* Pods-Runner.debug.xcconfig */,
+
7404320A2A2665D2993CC4A9 /* Pods-Runner.release.xcconfig */,
+
62C533E7959427EBD54BF4E0 /* Pods-Runner.profile.xcconfig */,
+
24C909BB605D55AC18D4D709 /* Pods-RunnerTests.debug.xcconfig */,
+
58C1A39422F3ADDA7073882C /* Pods-RunnerTests.release.xcconfig */,
+
CAFF337A6DF135B15E2E5A82 /* Pods-RunnerTests.profile.xcconfig */,
+
);
+
name = Pods;
+
path = Pods;
+
sourceTree = "<group>";
+
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
···
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
+
8AC347B174FB51D9D1783044 /* Pods */,
+
86A9EDA55647EB05647C404F /* Frameworks */,
);
sourceTree = "<group>";
};
···
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
+
0D16B9D95FB392A9811278BE /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
+
6A654D0E96DDFAB5016AAB44 /* Frameworks */,
);
buildRules = (
);
···
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+
5D065FE9468A69BB975A017A /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+
A11CDD673B8A553D9BF96957 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
···
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+
0D16B9D95FB392A9811278BE /* [CP] Check Pods Manifest.lock */ = {
+
isa = PBXShellScriptBuildPhase;
+
buildActionMask = 2147483647;
+
files = (
+
);
+
inputFileListPaths = (
+
);
+
inputPaths = (
+
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+
"${PODS_ROOT}/Manifest.lock",
+
);
+
name = "[CP] Check Pods Manifest.lock";
+
outputFileListPaths = (
+
);
+
outputPaths = (
+
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+
);
+
runOnlyForDeploymentPostprocessing = 0;
+
shellPath = /bin/sh;
+
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+
showEnvVarsInLog = 0;
+
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
···
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
+
5D065FE9468A69BB975A017A /* [CP] Check Pods Manifest.lock */ = {
+
isa = PBXShellScriptBuildPhase;
+
buildActionMask = 2147483647;
+
files = (
+
);
+
inputFileListPaths = (
+
);
+
inputPaths = (
+
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+
"${PODS_ROOT}/Manifest.lock",
+
);
+
name = "[CP] Check Pods Manifest.lock";
+
outputFileListPaths = (
+
);
+
outputPaths = (
+
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+
);
+
runOnlyForDeploymentPostprocessing = 0;
+
shellPath = /bin/sh;
+
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+
showEnvVarsInLog = 0;
+
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
···
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+
A11CDD673B8A553D9BF96957 /* [CP] Embed Pods Frameworks */ = {
+
isa = PBXShellScriptBuildPhase;
+
buildActionMask = 2147483647;
+
files = (
+
);
+
inputFileListPaths = (
+
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+
);
+
name = "[CP] Embed Pods Frameworks";
+
outputFileListPaths = (
+
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+
);
+
runOnlyForDeploymentPostprocessing = 0;
+
shellPath = /bin/sh;
+
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+
showEnvVarsInLog = 0;
+
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
···
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
···
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
+
baseConfigurationReference = 24C909BB605D55AC18D4D709 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
···
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
+
baseConfigurationReference = 58C1A39422F3ADDA7073882C /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
···
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
+
baseConfigurationReference = CAFF337A6DF135B15E2E5A82 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
···
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
···
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
+2
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
···
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
···
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
+3
ios/Runner.xcworkspace/contents.xcworkspacedata
···
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
+
<FileRef
+
location = "group:Pods/Pods.xcodeproj">
+
</FileRef>
</Workspace>