[READ ONLY MIRROR] Open Source TikTok alternative built on AT Protocol github.com/sprksocial/client
flutter atproto video dart
10
fork

Configure Feed

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

style: design system gradient & glass purge

+38 -211
+1 -1
assets/icons/grid_filled.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" width="23" height="22" fill="none" viewBox="0 0 23 22"><path fill="url(#a)" fill-rule="evenodd" d="M3.174 11.917c-.434 0-.651 0-.786.136-.134.135-.132.351-.128.782.011 1.196.048 2.211.162 3.065.173 1.287.54 2.345 1.377 3.181.836.837 1.894 1.204 3.181 1.377.854.114 1.869.15 3.065.162.43.004.647.006.782-.128.136-.135.136-.352.136-.786v-6.872c0-.433 0-.649-.134-.783s-.35-.134-.783-.134zM21.5 9.165c.004.431.006.647-.129.783-.134.136-.351.136-.785.136h-6.873c-.432 0-.648 0-.782-.135-.134-.134-.134-.35-.134-.782V2.294c0-.434 0-.65.135-.785.136-.135.352-.133.783-.129 1.196.012 2.21.048 3.065.163 1.287.173 2.345.54 3.181 1.376s1.203 1.894 1.376 3.181c.115.854.151 1.87.163 3.065" clip-rule="evenodd"/><path fill="url(#b)" fill-rule="evenodd" d="M10.963 2.294c0-.434 0-.65-.136-.785-.135-.135-.351-.133-.782-.129-1.196.012-2.211.048-3.065.163-1.287.173-2.345.54-3.181 1.376-.837.836-1.203 1.894-1.377 3.181-.114.854-.15 1.87-.162 3.065-.004.431-.006.647.128.783.135.136.352.136.786.136h6.873c.432 0 .648 0 .782-.135.134-.134.134-.35.134-.782zm1.834 17.412c0 .434 0 .651.135.786.136.134.352.132.783.128 1.196-.011 2.21-.048 3.065-.162 1.287-.173 2.345-.54 3.181-1.377.836-.836 1.203-1.894 1.376-3.18.115-.855.151-1.87.163-3.065.004-.432.006-.648-.129-.783-.134-.136-.351-.136-.785-.136h-6.873c-.432 0-.648 0-.782.134s-.134.35-.134.783z" clip-rule="evenodd"/><defs><linearGradient id="a" x1="-1.506" x2="34.097" y1="20.676" y2="3.428" gradientUnits="userSpaceOnUse"><stop stop-color="#ff97cd"/><stop offset="1" stop-color="#ff349d"/></linearGradient><linearGradient id="b" x1="-1.506" x2="34.097" y1="20.676" y2="3.428" gradientUnits="userSpaceOnUse"><stop stop-color="#ff97cd"/><stop offset="1" stop-color="#ff349d"/></linearGradient></defs></svg> 1 + <svg xmlns="http://www.w3.org/2000/svg" width="23" height="22" fill="none" viewBox="0 0 23 22"><path fill="#fff" fill-rule="evenodd" d="M3.174 11.917c-.434 0-.651 0-.786.136-.134.135-.132.351-.128.782.011 1.196.048 2.211.162 3.065.173 1.287.54 2.345 1.377 3.181.836.837 1.894 1.204 3.181 1.377.854.114 1.869.15 3.065.162.43.004.647.006.782-.128.136-.135.136-.352.136-.786v-6.872c0-.433 0-.649-.134-.783s-.35-.134-.783-.134zM21.5 9.165c.004.431.006.647-.129.783-.134.136-.351.136-.785.136h-6.873c-.432 0-.648 0-.782-.135-.134-.134-.134-.35-.134-.782V2.294c0-.434 0-.65.135-.785.136-.135.352-.133.783-.129 1.196.012 2.21.048 3.065.163 1.287.173 2.345.54 3.181 1.376s1.203 1.894 1.376 3.181c.115.854.151 1.87.163 3.065" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="M10.963 2.294c0-.434 0-.65-.136-.785-.135-.135-.351-.133-.782-.129-1.196.012-2.211.048-3.065.163-1.287.173-2.345.54-3.181 1.376-.837.836-1.203 1.894-1.377 3.181-.114.854-.15 1.87-.162 3.065-.004.431-.006.647.128.783.135.136.352.136.786.136h6.873c.432 0 .648 0 .782-.135.134-.134.134-.35.134-.782zm1.834 17.412c0 .434 0 .651.135.786.136.134.352.132.783.128 1.196-.011 2.21-.048 3.065-.162 1.287-.173 2.345-.54 3.181-1.377.836-.836 1.203-1.894 1.376-3.18.115-.855.151-1.87.163-3.065.004-.432.006-.648-.129-.783-.134-.136-.351-.136-.785-.136h-6.873c-.432 0-.648 0-.782.134s-.134.35-.134.783z" clip-rule="evenodd"/></svg>
+1 -1
assets/icons/like_filled.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" width="33" height="33" fill="none" viewBox="0 0 33 33"><path fill="url(#a)" d="M26.392 21.38c2.361-2.715 4.441-6.01 4.441-9.288 0-4.323-3.202-7.925-7.666-7.925-2.123 0-4.179.683-6.667 2.953-2.488-2.27-4.544-2.953-6.667-2.953-4.464 0-7.666 3.602-7.666 7.925 0 3.278 2.08 6.573 4.441 9.288 2.398 2.757 5.264 5.116 7.175 6.544l.31.214a4.515 4.515 0 0 0 5.125-.214l.759-.578c1.86-1.446 4.317-3.554 6.415-5.966"/><defs><linearGradient id="a" x1="12.459" x2="28.173" y1="28.834" y2="10.827" gradientUnits="userSpaceOnUse"><stop stop-color="#ff97cd"/><stop offset="1" stop-color="#ff349d"/></linearGradient></defs></svg> 1 + <svg xmlns="http://www.w3.org/2000/svg" width="33" height="33" fill="none" viewBox="0 0 33 33"><path fill="#fff" d="M26.392 21.38c2.361-2.715 4.441-6.01 4.441-9.288 0-4.323-3.202-7.925-7.666-7.925-2.123 0-4.179.683-6.667 2.953-2.488-2.27-4.544-2.953-6.667-2.953-4.464 0-7.666 3.602-7.666 7.925 0 3.278 2.08 6.573 4.441 9.288 2.398 2.757 5.264 5.116 7.175 6.544l.31.214a4.515 4.515 0 0 0 5.125-.214l.759-.578c1.86-1.446 4.317-3.554 6.415-5.966"/></svg>
+2 -2
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
··· 131 131 "kind" : "remoteSourceControl", 132 132 "location" : "https://github.com/PostHog/posthog-ios", 133 133 "state" : { 134 - "revision" : "d23e03625642e42b7358aa6e5ae9cfe71cd7c3e5", 135 - "version" : "3.46.0" 134 + "revision" : "09da1be6a614325a6a464c6d2017a9ac858d1b5a", 135 + "version" : "3.47.0" 136 136 } 137 137 }, 138 138 {
+2 -2
ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
··· 131 131 "kind" : "remoteSourceControl", 132 132 "location" : "https://github.com/PostHog/posthog-ios", 133 133 "state" : { 134 - "revision" : "d23e03625642e42b7358aa6e5ae9cfe71cd7c3e5", 135 - "version" : "3.46.0" 134 + "revision" : "09da1be6a614325a6a464c6d2017a9ac858d1b5a", 135 + "version" : "3.47.0" 136 136 } 137 137 }, 138 138 {
+3
ios/ci_scripts/ci_post_clone.sh
··· 10 10 git clone https://github.com/flutter/flutter.git --depth 1 -b stable $HOME/flutter 11 11 export PATH="$PATH:$HOME/flutter/bin" 12 12 13 + # Enable Swift Package Manager support. 14 + flutter config --enable-swift-package-manager 15 + 13 16 # Install Flutter artifacts for iOS (--ios), or macOS (--macos) platforms. 14 17 flutter precache --ios 15 18
+2 -4
lib/src/core/design_system/components/atoms/buttons/accent_button.dart
··· 1 1 import 'package:flutter/material.dart'; 2 - import 'package:gradient_borders/box_borders/gradient_box_border.dart'; 3 2 import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; 4 3 import 'package:spark/src/core/design_system/tokens/colors.dart'; 5 4 import 'package:spark/src/core/design_system/tokens/gradients.dart'; ··· 26 25 decoration: BoxDecoration( 27 26 gradient: AppGradients.accent, 28 27 borderRadius: BorderRadius.circular(500), // pill shape 29 - border: const GradientBoxBorder( 30 - gradient: AppGradients.glassStroke, 31 - width: 2, 28 + border: const Border.fromBorderSide( 29 + BorderSide(color: Color(0x25FFFFFF), width: 2), 32 30 ), 33 31 ), 34 32 child: Align(
+2 -5
lib/src/core/design_system/components/atoms/buttons/follow_pill_button.dart
··· 1 1 import 'dart:ui'; 2 2 3 3 import 'package:flutter/material.dart'; 4 - import 'package:gradient_borders/box_borders/gradient_box_border.dart'; 5 4 import 'package:spark/src/core/design_system/components/atoms/buttons/interactive_pressable.dart'; 6 5 import 'package:spark/src/core/design_system/tokens/colors.dart'; 7 - import 'package:spark/src/core/design_system/tokens/gradients.dart'; 8 6 import 'package:spark/src/core/design_system/tokens/typography.dart'; 9 7 10 8 class FollowPillButton extends StatelessWidget { ··· 34 32 decoration: const BoxDecoration( 35 33 color: Color(0x33FFFFFF), 36 34 borderRadius: BorderRadius.all(Radius.circular(500)), 37 - border: GradientBoxBorder( 38 - gradient: AppGradients.glassStroke, 39 - width: 2, 35 + border: Border.fromBorderSide( 36 + BorderSide(color: Color(0x25FFFFFF), width: 1), 40 37 ), 41 38 ), 42 39 child: Text(
+1 -7
lib/src/core/design_system/components/atoms/profile_tab_item.dart
··· 1 1 import 'package:flutter/material.dart'; 2 2 import 'package:spark/src/core/design_system/components/atoms/tab_item.dart'; 3 - import 'package:spark/src/core/design_system/tokens/gradients.dart'; 4 3 5 4 class ProfileTabItem extends StatelessWidget { 6 5 const ProfileTabItem({ ··· 20 19 Widget build(BuildContext context) { 21 20 final theme = Theme.of(context); 22 21 return AppTabItem( 23 - activeChild: ShaderMask( 24 - shaderCallback: (bounds) => 25 - AppGradients.gradientLinearPrimaryGradient.createShader(bounds), 26 - blendMode: BlendMode.srcIn, 27 - child: filledIcon, 28 - ), 22 + activeChild: filledIcon, 29 23 inactiveChild: icon, 30 24 isSelected: isSelected, 31 25 onTap: onTap,
-56
lib/src/core/design_system/components/atoms/toggles/glass_follow_button.dart
··· 1 - import 'dart:ui'; 2 - import 'package:flutter/material.dart'; 3 - import 'package:flutter/services.dart'; 4 - import 'package:spark/src/core/design_system/tokens/typography.dart'; 5 - 6 - class GlassFollowButton extends StatelessWidget { 7 - const GlassFollowButton({ 8 - required this.isFollowing, 9 - required this.onFollow, 10 - required this.onUnfollow, 11 - required this.followText, 12 - required this.unfollowText, 13 - super.key, 14 - }); 15 - 16 - final bool isFollowing; 17 - final VoidCallback onFollow; 18 - final VoidCallback onUnfollow; 19 - final String followText; 20 - final String unfollowText; 21 - 22 - @override 23 - Widget build(BuildContext context) { 24 - return GestureDetector( 25 - onTap: () { 26 - HapticFeedback.mediumImpact(); 27 - if (isFollowing) { 28 - onUnfollow(); 29 - } else { 30 - onFollow(); 31 - } 32 - }, 33 - child: ClipRRect( 34 - borderRadius: BorderRadius.circular(100), 35 - child: BackdropFilter( 36 - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), 37 - child: Container( 38 - constraints: const BoxConstraints(maxWidth: 80, maxHeight: 30), 39 - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), 40 - decoration: BoxDecoration( 41 - color: Colors.white.withAlpha(51), 42 - borderRadius: BorderRadius.circular(100), 43 - border: Border.all(color: Colors.white.withAlpha(37)), 44 - ), 45 - child: Center( 46 - child: Text( 47 - isFollowing ? unfollowText : followText, 48 - style: AppTypography.textExtraSmallMedium, 49 - ), 50 - ), 51 - ), 52 - ), 53 - ), 54 - ); 55 - } 56 - }
-60
lib/src/core/design_system/components/molecules/glass_avatar.dart
··· 1 - import 'dart:ui'; 2 - 3 - import 'package:flutter/material.dart'; 4 - import 'package:spark/src/core/ui/widgets/user_avatar.dart'; 5 - 6 - /// Circular avatar with subtle glass border similar to selected FeedTag style. 7 - class GlassAvatar extends StatelessWidget { 8 - const GlassAvatar({ 9 - required this.imageUrl, 10 - required this.username, 11 - super.key, 12 - this.size = 50.45, 13 - this.borderWidth = 1.5, 14 - }); 15 - 16 - final String imageUrl; 17 - final String username; 18 - final double size; // outer diameter including glass stroke 19 - final double borderWidth; 20 - 21 - @override 22 - Widget build(BuildContext context) { 23 - // Glass gradient stroke around the image (mimics Figma Glass/Stroke) 24 - const gradient = LinearGradient( 25 - begin: Alignment.topLeft, 26 - end: Alignment.bottomRight, 27 - colors: [ 28 - Color.fromARGB(77, 255, 255, 255), // 0.3 29 - Color.fromARGB(38, 255, 255, 255), // 0.15 30 - Color.fromARGB(26, 255, 255, 255), // 0.10 31 - Color.fromARGB(77, 255, 255, 255), // 0.3 32 - ], 33 - stops: [0.0, 0.5, 0.8, 1.0], 34 - ); 35 - 36 - return ClipOval( 37 - child: BackdropFilter( 38 - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), 39 - child: Container( 40 - width: size, 41 - height: size, 42 - decoration: const BoxDecoration( 43 - gradient: gradient, 44 - shape: BoxShape.circle, 45 - ), 46 - child: Padding( 47 - padding: EdgeInsets.all(borderWidth), 48 - child: ClipOval( 49 - child: UserAvatar( 50 - imageUrl: imageUrl, 51 - username: username, 52 - size: size - (borderWidth * 2), 53 - ), 54 - ), 55 - ), 56 - ), 57 - ), 58 - ); 59 - } 60 - }
+2 -5
lib/src/core/design_system/components/molecules/known_interactions_bar.dart
··· 1 1 import 'dart:ui'; 2 2 3 3 import 'package:flutter/material.dart'; 4 - import 'package:gradient_borders/box_borders/gradient_box_border.dart'; 5 4 import 'package:spark/src/core/design_system/components/atoms/avatar_stack.dart'; 6 5 import 'package:spark/src/core/design_system/components/atoms/icons.dart'; 7 - import 'package:spark/src/core/design_system/tokens/gradients.dart'; 8 6 import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; 9 7 import 'package:spark/src/core/ui/theme/theme.dart'; 10 8 ··· 88 86 decoration: const BoxDecoration( 89 87 color: Color(0x33FFFFFF), 90 88 borderRadius: BorderRadius.all(Radius.circular(500)), 91 - border: GradientBoxBorder( 92 - gradient: AppGradients.glassStroke, 93 - width: 2, 89 + border: Border.fromBorderSide( 90 + BorderSide(color: Color(0x25FFFFFF), width: 1), 94 91 ), 95 92 ), 96 93 child: Row(
+4 -1
lib/src/core/design_system/components/organisms/side_action_bar.dart
··· 157 157 isActive: widget.isLiked, 158 158 label: widget.likeCount, 159 159 icon: widget.isLiked 160 - ? AppIcons.likeFilled(size: 32) 160 + ? AppIcons.likeFilled( 161 + size: 32, 162 + color: Theme.of(context).colorScheme.primary, 163 + ) 161 164 : AppIcons.like(size: 32), 162 165 onTap: widget.onLike, 163 166 ),
+3 -2
lib/src/core/design_system/templates/chat_list_page_template.dart
··· 1 1 import 'package:flutter/material.dart'; 2 2 import 'package:spark/src/core/design_system/components/atoms/icons.dart'; 3 - import 'package:spark/src/core/design_system/components/molecules/glass_avatar.dart'; 4 3 import 'package:spark/src/core/design_system/tokens/typography.dart'; 4 + import 'package:spark/src/core/ui/widgets/user_avatar.dart'; 5 5 6 6 class ChatListItemData { 7 7 const ChatListItemData({ ··· 98 98 onTap: onTap, 99 99 horizontalTitleGap: 12, 100 100 contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), 101 - leading: GlassAvatar( 101 + leading: UserAvatar( 102 102 imageUrl: data.avatarUrl ?? '', 103 103 username: data.handle, 104 + size: 50.45, 104 105 ), 105 106 title: Row( 106 107 children: [
-25
lib/src/core/design_system/tokens/gradients.dart
··· 118 118 ], 119 119 ); 120 120 121 - static const glassStroke = LinearGradient( 122 - begin: Alignment(-0.8200000000000001, -0.8), 123 - end: Alignment(0.72, 0.8200000000000001), 124 - transform: GradientRotation(45 * (math.pi / 180)), 125 - stops: [0, 0.3702, 0.7212, 1], 126 - colors: [ 127 - Color(0x3FFFFFFF), // was 6 * 5, now 9 * 7 128 - Color(0x1CFFFFFF), // was 3 * 5, now 4 * 7 129 - Color(0x15FFFFFF), // was 2 * 5, now 3 * 7 130 - Color(0x31FFFFFF), // was 6 * 5, now 7 * 7 131 - ], 132 - ); 133 - 134 - static const glassStrokeLight = LinearGradient( 135 - begin: Alignment(-0.8200000000000001, -0.8), 136 - end: Alignment(0.72, 0.8200000000000001), 137 - stops: [0, 0.3702, 0.7212, 1], 138 - colors: [ 139 - Color(0x4d000000), 140 - Color(0x26000000), 141 - Color(0x1a000000), 142 - Color(0x4d000000), 143 - ], 144 - ); 145 - 146 121 static const darkStroke = LinearGradient( 147 122 begin: Alignment(-0.8200000000000001, -0.8), 148 123 end: Alignment(0.72, 0.8200000000000001),
+4 -1
lib/src/core/ui/widgets/heart_animation.dart
··· 84 84 scale: _scaleAnimation.value, 85 85 child: Opacity( 86 86 opacity: _opacityAnimation.value, 87 - child: AppIcons.likeFilled(size: 100), 87 + child: AppIcons.likeFilled( 88 + size: 100, 89 + color: Theme.of(context).colorScheme.primary, 90 + ), 88 91 ), 89 92 ); 90 93 },
+9 -4
lib/src/features/profile/ui/pages/profile_page.dart
··· 537 537 final tabs = [ 538 538 ProfileTabItem( 539 539 icon: AppIcons.grid(color: inactiveColor), 540 - filledIcon: AppIcons.gridFilled(), 540 + filledIcon: AppIcons.gridFilled( 541 + color: Theme.of(context).colorScheme.primary, 542 + ), 541 543 isSelected: activeIndex == 0, 542 544 onTap: () { 543 545 setState(() { ··· 547 549 ), 548 550 ProfileTabItem( 549 551 icon: AppIcons.repost(color: inactiveColor), 550 - filledIcon: 551 - AppIcons.repost(), // No filled variant exists, use same icon 552 + filledIcon: AppIcons.repost( 553 + color: Theme.of(context).colorScheme.primary, 554 + ), // No filled variant exists, use same icon 552 555 isSelected: activeIndex == 1, 553 556 onTap: () { 554 557 setState(() { ··· 563 566 tabs.add( 564 567 ProfileTabItem( 565 568 icon: AppIcons.profileLiked(color: inactiveColor), 566 - filledIcon: AppIcons.likeFilled(), 569 + filledIcon: AppIcons.likeFilled( 570 + color: Theme.of(context).colorScheme.primary, 571 + ), 567 572 isSelected: activeIndex == 2, 568 573 onTap: () { 569 574 setState(() {
-8
pubspec.lock
··· 837 837 url: "https://pub.dev" 838 838 source: hosted 839 839 version: "8.0.2" 840 - gradient_borders: 841 - dependency: "direct main" 842 - description: 843 - name: gradient_borders 844 - sha256: "492bc88ab8d88a4117a7f00e525a669b65f19973bea7ee677f9d9de7603bf037" 845 - url: "https://pub.dev" 846 - source: hosted 847 - version: "1.0.2" 848 840 graphs: 849 841 dependency: transitive 850 842 description:
-1
pubspec.yaml
··· 46 46 fvp: ^0.35.2 47 47 get_it: ^9.2.1 48 48 google_fonts: ^8.0.2 49 - gradient_borders: ^1.0.1 50 49 http: ^1.2.0 51 50 image: ^4.8.0 52 51 image_picker: ^1.0.7
+2 -26
widgetbook/lib/atoms/follow_buttons_and_toggle.dart
··· 1 1 import 'package:flutter/material.dart'; 2 + import 'package:spark/src/core/design_system/components/atoms/toggles/follow_button.dart'; 3 + import 'package:widgetbook/widgetbook.dart'; 2 4 import 'package:widgetbook_annotation/widgetbook_annotation.dart'; 3 - import 'package:widgetbook/widgetbook.dart'; 4 - import 'package:spark/src/core/design_system/components/atoms/toggles/follow_button.dart'; 5 - import 'package:spark/src/core/design_system/components/atoms/toggles/glass_follow_button.dart'; 6 5 7 6 @UseCase(name: 'follow_states', type: FollowButton) 8 7 Widget buildFollowButtonFollowStatesUseCase(BuildContext context) { ··· 26 25 ), 27 26 ); 28 27 } 29 - 30 - @UseCase(name: 'glass_follow_states', type: GlassFollowButton) 31 - Widget buildGlassFollowButtonGlassFollowStatesUseCase(BuildContext context) { 32 - final isFollowing = context.knobs.boolean( 33 - label: 'is_following', 34 - initialValue: false, 35 - ); 36 - return Center( 37 - child: GlassFollowButton( 38 - isFollowing: isFollowing, 39 - onFollow: () => print('Follow pressed'), 40 - onUnfollow: () => print('Unfollow pressed'), 41 - followText: context.knobs.string( 42 - label: 'follow_text', 43 - initialValue: 'Follow', 44 - ), 45 - unfollowText: context.knobs.string( 46 - label: 'unfollow_text', 47 - initialValue: 'Unfollow', 48 - ), 49 - ), 50 - ); 51 - }