[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.

Updates

Coisas, tema, icones, cores... varias coisas.

C3B 03a040c0 0de91e4d

+1380 -551
+1 -1
android/app/src/main/AndroidManifest.xml
··· 1 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"> 2 2 <application 3 - android:label="sparksocial" 3 + android:label="Spark" 4 4 android:name="${applicationName}" 5 5 android:icon="@mipmap/ic_launcher"> 6 6 <activity
android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

+9
android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> 3 + <background android:drawable="@color/ic_launcher_background"/> 4 + <foreground> 5 + <inset 6 + android:drawable="@drawable/ic_launcher_foreground" 7 + android:inset="16%" /> 8 + </foreground> 9 + </adaptive-icon>
android/app/src/main/res/mipmap-hdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-mdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xhdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png

This is a binary file and will not be displayed.

+4
android/app/src/main/res/values/colors.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <resources> 3 + <color name="ic_launcher_background">#FFFFFF</color> 4 + </resources>
assets/images/icon.png

This is a binary file and will not be displayed.

+2 -2
ios/Runner.xcodeproj/project.pbxproj
··· 539 539 isa = XCBuildConfiguration; 540 540 buildSettings = { 541 541 ALWAYS_SEARCH_USER_PATHS = NO; 542 - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 542 + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; 543 543 CLANG_ANALYZER_NONNULL = YES; 544 544 CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 545 545 CLANG_CXX_LIBRARY = "libc++"; ··· 596 596 isa = XCBuildConfiguration; 597 597 buildSettings = { 598 598 ALWAYS_SEARCH_USER_PATHS = NO; 599 - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 599 + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; 600 600 CLANG_ANALYZER_NONNULL = YES; 601 601 CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 602 602 CLANG_CXX_LIBRARY = "libc++";
+1 -122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
··· 1 - { 2 - "images" : [ 3 - { 4 - "size" : "20x20", 5 - "idiom" : "iphone", 6 - "filename" : "Icon-App-20x20@2x.png", 7 - "scale" : "2x" 8 - }, 9 - { 10 - "size" : "20x20", 11 - "idiom" : "iphone", 12 - "filename" : "Icon-App-20x20@3x.png", 13 - "scale" : "3x" 14 - }, 15 - { 16 - "size" : "29x29", 17 - "idiom" : "iphone", 18 - "filename" : "Icon-App-29x29@1x.png", 19 - "scale" : "1x" 20 - }, 21 - { 22 - "size" : "29x29", 23 - "idiom" : "iphone", 24 - "filename" : "Icon-App-29x29@2x.png", 25 - "scale" : "2x" 26 - }, 27 - { 28 - "size" : "29x29", 29 - "idiom" : "iphone", 30 - "filename" : "Icon-App-29x29@3x.png", 31 - "scale" : "3x" 32 - }, 33 - { 34 - "size" : "40x40", 35 - "idiom" : "iphone", 36 - "filename" : "Icon-App-40x40@2x.png", 37 - "scale" : "2x" 38 - }, 39 - { 40 - "size" : "40x40", 41 - "idiom" : "iphone", 42 - "filename" : "Icon-App-40x40@3x.png", 43 - "scale" : "3x" 44 - }, 45 - { 46 - "size" : "60x60", 47 - "idiom" : "iphone", 48 - "filename" : "Icon-App-60x60@2x.png", 49 - "scale" : "2x" 50 - }, 51 - { 52 - "size" : "60x60", 53 - "idiom" : "iphone", 54 - "filename" : "Icon-App-60x60@3x.png", 55 - "scale" : "3x" 56 - }, 57 - { 58 - "size" : "20x20", 59 - "idiom" : "ipad", 60 - "filename" : "Icon-App-20x20@1x.png", 61 - "scale" : "1x" 62 - }, 63 - { 64 - "size" : "20x20", 65 - "idiom" : "ipad", 66 - "filename" : "Icon-App-20x20@2x.png", 67 - "scale" : "2x" 68 - }, 69 - { 70 - "size" : "29x29", 71 - "idiom" : "ipad", 72 - "filename" : "Icon-App-29x29@1x.png", 73 - "scale" : "1x" 74 - }, 75 - { 76 - "size" : "29x29", 77 - "idiom" : "ipad", 78 - "filename" : "Icon-App-29x29@2x.png", 79 - "scale" : "2x" 80 - }, 81 - { 82 - "size" : "40x40", 83 - "idiom" : "ipad", 84 - "filename" : "Icon-App-40x40@1x.png", 85 - "scale" : "1x" 86 - }, 87 - { 88 - "size" : "40x40", 89 - "idiom" : "ipad", 90 - "filename" : "Icon-App-40x40@2x.png", 91 - "scale" : "2x" 92 - }, 93 - { 94 - "size" : "76x76", 95 - "idiom" : "ipad", 96 - "filename" : "Icon-App-76x76@1x.png", 97 - "scale" : "1x" 98 - }, 99 - { 100 - "size" : "76x76", 101 - "idiom" : "ipad", 102 - "filename" : "Icon-App-76x76@2x.png", 103 - "scale" : "2x" 104 - }, 105 - { 106 - "size" : "83.5x83.5", 107 - "idiom" : "ipad", 108 - "filename" : "Icon-App-83.5x83.5@2x.png", 109 - "scale" : "2x" 110 - }, 111 - { 112 - "size" : "1024x1024", 113 - "idiom" : "ios-marketing", 114 - "filename" : "Icon-App-1024x1024@1x.png", 115 - "scale" : "1x" 116 - } 117 - ], 118 - "info" : { 119 - "version" : 1, 120 - "author" : "xcode" 121 - } 122 - } 1 + {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png

This is a binary file and will not be displayed.

+2 -2
ios/Runner/Info.plist
··· 5 5 <key>CFBundleDevelopmentRegion</key> 6 6 <string>$(DEVELOPMENT_LANGUAGE)</string> 7 7 <key>CFBundleDisplayName</key> 8 - <string>Sparksocial</string> 8 + <string>Spark</string> 9 9 <key>CFBundleExecutable</key> 10 10 <string>$(EXECUTABLE_NAME)</string> 11 11 <key>CFBundleIdentifier</key> ··· 13 13 <key>CFBundleInfoDictionaryVersion</key> 14 14 <string>6.0</string> 15 15 <key>CFBundleName</key> 16 - <string>sparksocial</string> 16 + <string>spark</string> 17 17 <key>CFBundlePackageType</key> 18 18 <string>APPL</string> 19 19 <key>CFBundleShortVersionString</key>
+25 -33
lib/main.dart
··· 8 8 import 'screens/messages_screen.dart'; 9 9 import 'screens/profile_screen.dart'; 10 10 import 'screens/splash_screen.dart'; 11 + import 'utils/app_colors.dart'; 12 + import 'utils/app_theme.dart'; 11 13 12 14 void main() { 13 15 WidgetsFlutterBinding.ensureInitialized(); ··· 23 25 24 26 @override 25 27 Widget build(BuildContext context) { 26 - return ChangeNotifierProvider( 27 - create: (_) => NavigationProvider(), 28 - child: CupertinoApp( 29 - title: 'TikTok Clone', 30 - theme: const CupertinoThemeData( 31 - brightness: Brightness.light, 32 - primaryColor: CupertinoColors.systemPink, 28 + // We'll use a builder to get access to the platform brightness 29 + return CupertinoTheme( 30 + data: AppTheme.theme, 31 + child: ChangeNotifierProvider( 32 + create: (_) => NavigationProvider(), 33 + child: CupertinoApp( 34 + title: 'Spark', 35 + theme: AppTheme.theme, 36 + home: const SplashScreen(), 37 + routes: { 38 + '/home': (context) => const MainScreen(), 39 + }, 33 40 ), 34 - home: const SplashScreen(), 35 - routes: { 36 - '/home': (context) => const MainScreen(), 37 - }, 38 41 ), 39 42 ); 40 43 } ··· 57 60 @override 58 61 Widget build(BuildContext context) { 59 62 final navigationProvider = Provider.of<NavigationProvider>(context); 63 + final bool isHomePage = navigationProvider.currentIndex == 0; 60 64 61 65 // Creating the list of screens for navigation 62 66 final List<Widget> screens = [ ··· 68 72 ]; 69 73 70 74 return CupertinoPageScaffold( 71 - backgroundColor: CupertinoColors.black, 75 + backgroundColor: AppTheme.getBackgroundColor(context, isHomePage), 72 76 child: Stack( 73 77 children: [ 74 78 // Main content ··· 84 88 bottom: 0, 85 89 child: Container( 86 90 decoration: BoxDecoration( 87 - color: navigationProvider.currentIndex == 0 88 - ? CupertinoColors.black 89 - : CupertinoColors.systemBackground, 90 - border: const Border( 91 + color: AppTheme.getNavBackgroundColor(context, isHomePage), 92 + border: Border( 91 93 top: BorderSide( 92 - color: CupertinoColors.systemGrey5, 94 + color: AppColors.border, 93 95 width: 0.5, 94 96 ), 95 97 ), ··· 144 146 Widget _buildNavItem(BuildContext context, int index, String label, IconData iconOutline, IconData iconFilled) { 145 147 final navigationProvider = Provider.of<NavigationProvider>(context); 146 148 final bool isSelected = navigationProvider.currentIndex == index; 147 - final bool isDarkMode = navigationProvider.currentIndex == 0; 149 + final bool isHomePage = navigationProvider.currentIndex == 0; 148 150 149 151 return CupertinoButton( 150 152 padding: EdgeInsets.zero, ··· 154 156 child: Icon( 155 157 isSelected ? iconFilled : iconOutline, 156 158 color: isSelected 157 - ? (isDarkMode ? CupertinoColors.white : CupertinoColors.activeBlue) 158 - : (isDarkMode ? CupertinoColors.systemGrey : CupertinoColors.systemGrey), 159 - size: 26, // Slightly larger icon since we removed the text 159 + ? AppTheme.getSelectedIconColor(context, isHomePage) 160 + : AppTheme.getUnselectedIconColor(context, isHomePage), 161 + size: 26, 160 162 ), 161 163 ); 162 164 } 163 165 164 166 Widget _buildCreateButton(BuildContext context) { 165 - final navigationProvider = Provider.of<NavigationProvider>(context); 166 - final bool isDarkMode = navigationProvider.currentIndex == 0; 167 - 168 167 return CupertinoButton( 169 168 padding: EdgeInsets.zero, 170 169 onPressed: () { ··· 179 178 width: 48, 180 179 height: 36, 181 180 decoration: BoxDecoration( 182 - gradient: const LinearGradient( 183 - colors: [ 184 - CupertinoColors.systemPink, 185 - CupertinoColors.systemBlue, 186 - ], 187 - begin: Alignment.bottomLeft, 188 - end: Alignment.topRight, 189 - ), 181 + color: AppColors.primary, 190 182 borderRadius: BorderRadius.circular(10), 191 183 ), 192 184 child: const Center( 193 185 child: Icon( 194 186 Ionicons.add, 195 - color: CupertinoColors.white, 187 + color: AppColors.white, 196 188 size: 24, 197 189 ), 198 190 ),
+1 -1
lib/screens/home_screen.dart
··· 89 89 // Sample data for the video item 90 90 final String username = 'username'; 91 91 final String description = 'Video caption goes here'; 92 - final List<String> hashtags = ['tiktok', 'viral', 'trending']; 92 + final List<String> hashtags = ['spark', 'viral', 'trending']; 93 93 94 94 return Container( 95 95 // Use constraints to ensure the video fits within available space
+130 -153
lib/screens/messages_screen.dart
··· 1 1 import 'package:flutter/cupertino.dart'; 2 2 import 'package:ionicons/ionicons.dart'; 3 + import '../widgets/messages/message_list.dart'; 4 + import '../widgets/activities/activity_list.dart'; 5 + import '../widgets/activities/activity_icon.dart'; 6 + import '../utils/app_colors.dart'; 7 + import '../utils/app_theme.dart'; 3 8 4 - class MessagesScreen extends StatelessWidget { 9 + class MessagesScreen extends StatefulWidget { 5 10 const MessagesScreen({super.key}); 6 11 7 12 @override 13 + State<MessagesScreen> createState() => _MessagesScreenState(); 14 + } 15 + 16 + class _MessagesScreenState extends State<MessagesScreen> { 17 + int _selectedTabIndex = 0; 18 + 19 + // Mock data for messages 20 + final List<MessageData> _messages = List.generate( 21 + 15, 22 + (index) => MessageData( 23 + id: 'msg_$index', 24 + username: 'user${index + 1}', 25 + messagePreview: index % 2 == 0 26 + ? 'Check out my latest video! 🔥' 27 + : 'Hey, how are you doing?', 28 + timeString: index % 4 == 0 29 + ? 'Just now' 30 + : index % 4 == 1 31 + ? '5m ago' 32 + : index % 4 == 2 33 + ? '1h ago' 34 + : 'Yesterday', 35 + unreadCount: index % 3 == 0 ? 1 : null, 36 + ), 37 + ); 38 + 39 + // Mock data for activities 40 + final List<ActivityData> _activities = List.generate( 41 + 15, 42 + (index) { 43 + final ActivityType type = ActivityType.values[index % ActivityType.values.length]; 44 + String? additionalInfo; 45 + 46 + if (type == ActivityType.comment) { 47 + additionalInfo = 'Wow, this looks amazing! 🔥'; 48 + } else if (type == ActivityType.like) { 49 + additionalInfo = null; 50 + } else { 51 + additionalInfo = null; 52 + } 53 + 54 + return ActivityData( 55 + id: 'act_$index', 56 + username: 'user${index + 1}', 57 + type: type, 58 + timeString: index % 4 == 0 59 + ? 'Just now' 60 + : index % 4 == 1 61 + ? '5m ago' 62 + : index % 4 == 2 63 + ? '1h ago' 64 + : 'Yesterday', 65 + additionalInfo: additionalInfo, 66 + targetContentId: 'content_$index', 67 + ); 68 + }, 69 + ); 70 + 71 + @override 8 72 Widget build(BuildContext context) { 73 + final brightness = MediaQuery.of(context).platformBrightness; 74 + final isDarkMode = brightness == Brightness.dark; 75 + 9 76 return CupertinoPageScaffold( 10 - backgroundColor: CupertinoColors.systemBackground, 11 - navigationBar: const CupertinoNavigationBar( 12 - middle: Text('Messages'), 13 - trailing: Icon(Ionicons.create_outline), 14 - backgroundColor: CupertinoColors.systemBackground, 77 + backgroundColor: AppTheme.getBackgroundColor(context, false), 78 + navigationBar: CupertinoNavigationBar( 79 + middle: Text('Messages', style: TextStyle(color: AppTheme.getTextColor(context))), 80 + trailing: Icon(Ionicons.create_outline, color: AppTheme.getTextColor(context)), 81 + backgroundColor: isDarkMode ? AppColors.deepPurple : AppColors.background, 15 82 ), 16 83 child: SafeArea( 17 84 child: Column( 18 85 children: [ 86 + // Tab selector 87 + Padding( 88 + padding: const EdgeInsets.only(top: 8.0), 89 + child: CupertinoSegmentedControl<int>( 90 + children: const { 91 + 0: Padding( 92 + padding: EdgeInsets.symmetric(horizontal: 30), 93 + child: Text('Messages'), 94 + ), 95 + 1: Padding( 96 + padding: EdgeInsets.symmetric(horizontal: 30), 97 + child: Text('Activities'), 98 + ), 99 + }, 100 + onValueChanged: (value) { 101 + setState(() { 102 + _selectedTabIndex = value; 103 + }); 104 + }, 105 + groupValue: _selectedTabIndex, 106 + selectedColor: AppColors.primary, 107 + unselectedColor: isDarkMode ? AppColors.deepPurple : AppColors.lightLavender, 108 + borderColor: AppColors.primary, 109 + ), 110 + ), 111 + 19 112 // Search bar 20 113 Padding( 21 114 padding: const EdgeInsets.all(16.0), 22 115 child: CupertinoSearchTextField( 23 116 placeholder: 'Search', 24 - prefixIcon: const Icon(Ionicons.search_outline), 117 + prefixIcon: Icon( 118 + Ionicons.search_outline, 119 + color: AppTheme.getSecondaryTextColor(context), 120 + ), 25 121 onChanged: (value) { 26 122 // Handle search 27 123 }, 124 + style: TextStyle( 125 + color: AppTheme.getTextColor(context), 126 + ), 127 + placeholderStyle: TextStyle( 128 + color: AppTheme.getSecondaryTextColor(context), 129 + ), 130 + backgroundColor: isDarkMode ? AppColors.deepPurple : AppColors.white, 28 131 ), 29 132 ), 30 133 31 - // Message list 134 + // Content based on selected tab 32 135 Expanded( 33 - child: ListView.builder( 34 - itemCount: 15, 35 - itemBuilder: (context, index) { 36 - return MessageListItem(index: index); 37 - }, 38 - ), 136 + child: _selectedTabIndex == 0 137 + ? _buildMessagesTab() 138 + : _buildActivitiesTab(), 39 139 ), 40 140 ], 41 141 ), 42 142 ), 43 143 ); 44 144 } 45 - } 46 - 47 - class MessageListItem extends StatelessWidget { 48 - final int index; 145 + 146 + Widget _buildMessagesTab() { 147 + return MessageList( 148 + messages: _messages, 149 + onMessageTap: (message) { 150 + // Handle message tap 151 + print('Tapped on message: ${message.id}'); 152 + }, 153 + ); 154 + } 49 155 50 - const MessageListItem({super.key, required this.index}); 51 - 52 - @override 53 - Widget build(BuildContext context) { 54 - // Generate mock data based on index 55 - final bool hasUnread = index % 3 == 0; 56 - final String username = '@user${index + 1}'; 57 - final String message = index % 2 == 0 58 - ? 'Check out my latest video! 🔥' 59 - : 'Hey, how are you doing?'; 60 - final String time = index % 4 == 0 61 - ? 'Just now' 62 - : index % 4 == 1 63 - ? '5m ago' 64 - : index % 4 == 2 65 - ? '1h ago' 66 - : 'Yesterday'; 67 - 68 - return GestureDetector( 69 - onTap: () { 70 - // Navigate to chat detail 156 + Widget _buildActivitiesTab() { 157 + return ActivityList( 158 + activities: _activities, 159 + onActivityTap: (activity) { 160 + // Handle activity tap 161 + print('Tapped on activity: ${activity.id}'); 71 162 }, 72 - child: Container( 73 - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 74 - decoration: BoxDecoration( 75 - border: Border( 76 - bottom: BorderSide( 77 - color: CupertinoColors.systemGrey5, 78 - width: 0.5, 79 - ), 80 - ), 81 - ), 82 - child: Row( 83 - children: [ 84 - // Profile image 85 - Stack( 86 - children: [ 87 - Container( 88 - width: 60, 89 - height: 60, 90 - decoration: BoxDecoration( 91 - color: index % 2 == 0 92 - ? CupertinoColors.systemPurple.withOpacity(0.2) 93 - : CupertinoColors.systemTeal.withOpacity(0.2), 94 - shape: BoxShape.circle, 95 - ), 96 - child: Center( 97 - child: Icon( 98 - Ionicons.person_outline, 99 - color: index % 2 == 0 100 - ? CupertinoColors.systemPurple 101 - : CupertinoColors.systemTeal, 102 - size: 30, 103 - ), 104 - ), 105 - ), 106 - if (hasUnread) 107 - Positioned( 108 - top: 0, 109 - right: 0, 110 - child: Container( 111 - width: 16, 112 - height: 16, 113 - decoration: const BoxDecoration( 114 - color: CupertinoColors.systemRed, 115 - shape: BoxShape.circle, 116 - ), 117 - child: const Center( 118 - child: Text( 119 - '1', 120 - style: TextStyle( 121 - color: CupertinoColors.white, 122 - fontSize: 10, 123 - fontWeight: FontWeight.bold, 124 - ), 125 - ), 126 - ), 127 - ), 128 - ), 129 - ], 130 - ), 131 - 132 - const SizedBox(width: 12), 133 - 134 - // Message content 135 - Expanded( 136 - child: Column( 137 - crossAxisAlignment: CrossAxisAlignment.start, 138 - children: [ 139 - Row( 140 - mainAxisAlignment: MainAxisAlignment.spaceBetween, 141 - children: [ 142 - Text( 143 - username, 144 - style: TextStyle( 145 - fontWeight: hasUnread ? FontWeight.bold : FontWeight.normal, 146 - fontSize: 16, 147 - ), 148 - ), 149 - Text( 150 - time, 151 - style: TextStyle( 152 - color: hasUnread 153 - ? CupertinoColors.activeBlue 154 - : CupertinoColors.systemGrey, 155 - fontSize: 12, 156 - ), 157 - ), 158 - ], 159 - ), 160 - const SizedBox(height: 4), 161 - Text( 162 - message, 163 - style: TextStyle( 164 - color: hasUnread 165 - ? CupertinoColors.label 166 - : CupertinoColors.systemGrey, 167 - fontWeight: hasUnread ? FontWeight.w500 : FontWeight.normal, 168 - ), 169 - overflow: TextOverflow.ellipsis, 170 - ), 171 - ], 172 - ), 173 - ), 174 - 175 - const SizedBox(width: 8), 176 - 177 - // Right action 178 - Icon( 179 - Ionicons.chevron_forward, 180 - color: CupertinoColors.systemGrey2, 181 - size: 16, 182 - ), 183 - ], 184 - ), 185 - ), 186 163 ); 187 164 } 188 165 }
+237 -211
lib/screens/profile_screen.dart
··· 1 1 import 'package:flutter/cupertino.dart'; 2 + import 'package:flutter/material.dart' show Colors; 2 3 import 'package:ionicons/ionicons.dart'; 4 + import '../utils/app_colors.dart'; 5 + import '../utils/app_theme.dart'; 3 6 4 7 class ProfileScreen extends StatefulWidget { 5 8 const ProfileScreen({super.key}); ··· 11 14 class _ProfileScreenState extends State<ProfileScreen> { 12 15 int _selectedTabIndex = 0; 13 16 14 - final List<Widget> _tabs = [ 15 - // Videos tab 16 - GridView.builder( 17 - padding: const EdgeInsets.all(1), 18 - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 19 - crossAxisCount: 3, 20 - childAspectRatio: 2/3, 21 - crossAxisSpacing: 1, 22 - mainAxisSpacing: 1, 23 - ), 24 - itemCount: 15, 25 - itemBuilder: (context, index) { 26 - return Container( 27 - color: index % 3 == 0 28 - ? CupertinoColors.systemIndigo.withOpacity(0.7) 29 - : index % 3 == 1 30 - ? CupertinoColors.systemPurple.withOpacity(0.7) 31 - : CupertinoColors.systemTeal.withOpacity(0.7), 32 - child: Stack( 33 - children: [ 34 - Center( 35 - child: Icon( 36 - Ionicons.play_outline, 37 - color: CupertinoColors.white.withOpacity(0.8), 38 - ), 39 - ), 40 - Positioned( 41 - bottom: 5, 42 - left: 5, 43 - child: Row( 44 - children: [ 45 - const Icon( 46 - Ionicons.eye_outline, 47 - color: CupertinoColors.white, 48 - size: 12, 49 - ), 50 - const SizedBox(width: 4), 51 - Text( 52 - '${(index + 1) * 1000}', 53 - style: const TextStyle( 54 - color: CupertinoColors.white, 55 - fontSize: 12, 56 - ), 57 - ), 58 - ], 59 - ), 60 - ), 61 - ], 62 - ), 63 - ); 64 - }, 65 - ), 66 - 67 - // Liked tab (similar layout but different content) 68 - GridView.builder( 69 - padding: const EdgeInsets.all(1), 70 - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 71 - crossAxisCount: 3, 72 - childAspectRatio: 2/3, 73 - crossAxisSpacing: 1, 74 - mainAxisSpacing: 1, 75 - ), 76 - itemCount: 9, 77 - itemBuilder: (context, index) { 78 - return Container( 79 - color: index % 3 == 0 80 - ? CupertinoColors.systemOrange.withOpacity(0.7) 81 - : index % 3 == 1 82 - ? CupertinoColors.systemPink.withOpacity(0.7) 83 - : CupertinoColors.systemRed.withOpacity(0.7), 84 - child: Stack( 85 - children: [ 86 - Center( 87 - child: Icon( 88 - Ionicons.play_outline, 89 - color: CupertinoColors.white.withOpacity(0.8), 90 - ), 91 - ), 92 - Positioned( 93 - bottom: 5, 94 - left: 5, 95 - child: Row( 96 - children: [ 97 - const Icon( 98 - Ionicons.heart_outline, 99 - color: CupertinoColors.white, 100 - size: 12, 101 - ), 102 - const SizedBox(width: 4), 103 - Text( 104 - '${(index + 1) * 1000}', 105 - style: const TextStyle( 106 - color: CupertinoColors.white, 107 - fontSize: 12, 108 - ), 109 - ), 110 - ], 111 - ), 112 - ), 113 - ], 114 - ), 115 - ); 116 - }, 117 - ), 118 - 119 - // Private tab 120 - Center( 121 - child: Column( 122 - mainAxisAlignment: MainAxisAlignment.center, 123 - children: [ 124 - const Icon( 125 - Ionicons.lock_closed_outline, 126 - size: 60, 127 - color: CupertinoColors.systemGrey, 128 - ), 129 - const SizedBox(height: 20), 130 - const Text( 131 - 'Private videos', 132 - style: TextStyle( 133 - fontWeight: FontWeight.bold, 134 - fontSize: 18, 135 - ), 136 - ), 137 - const SizedBox(height: 10), 138 - const Text( 139 - 'Videos you\'ve saved to private will appear here', 140 - style: TextStyle( 141 - color: CupertinoColors.systemGrey, 142 - ), 143 - textAlign: TextAlign.center, 144 - ), 145 - ], 146 - ), 147 - ), 148 - ]; 149 - 150 17 @override 151 18 Widget build(BuildContext context) { 19 + final brightness = MediaQuery.of(context).platformBrightness; 20 + final isDarkMode = brightness == Brightness.dark; 21 + 152 22 return CupertinoPageScaffold( 153 - backgroundColor: CupertinoColors.systemBackground, 154 - navigationBar: const CupertinoNavigationBar( 155 - middle: Text('@username'), 156 - trailing: Icon(Ionicons.menu_outline), 157 - backgroundColor: CupertinoColors.systemBackground, 23 + backgroundColor: AppTheme.getBackgroundColor(context, false), 24 + navigationBar: CupertinoNavigationBar( 25 + middle: Text( 26 + '@username', 27 + style: TextStyle(color: AppTheme.getTextColor(context)), 28 + ), 29 + trailing: Icon( 30 + Ionicons.menu_outline, 31 + color: AppTheme.getTextColor(context), 32 + ), 33 + backgroundColor: isDarkMode ? AppColors.deepPurple : AppColors.background, 158 34 ), 159 35 child: SafeArea( 160 36 child: Column( ··· 169 45 width: 100, 170 46 height: 100, 171 47 decoration: BoxDecoration( 172 - color: CupertinoColors.systemGrey6, 48 + color: isDarkMode ? AppColors.darkPurple : AppColors.lightLavender, 173 49 shape: BoxShape.circle, 174 50 border: Border.all( 175 - color: CupertinoColors.systemGrey5, 51 + color: isDarkMode ? AppColors.darkPurple : AppColors.lightLavender, 176 52 width: 2, 177 53 ), 178 54 ), 179 - child: const Center( 55 + child: Center( 180 56 child: Icon( 181 57 Ionicons.person_outline, 182 58 size: 50, 183 - color: CupertinoColors.systemGrey, 59 + color: isDarkMode ? AppColors.textLight : AppColors.textSecondary, 184 60 ), 185 61 ), 186 62 ), ··· 188 64 const SizedBox(height: 12), 189 65 190 66 // Username 191 - const Text( 67 + Text( 192 68 '@username', 193 69 style: TextStyle( 194 70 fontWeight: FontWeight.bold, 195 71 fontSize: 18, 72 + color: AppTheme.getTextColor(context), 196 73 ), 197 74 ), 198 75 ··· 202 79 Row( 203 80 mainAxisAlignment: MainAxisAlignment.spaceEvenly, 204 81 children: [ 205 - _buildStatColumn('150', 'Following'), 206 - _buildStatColumn('1.2M', 'Followers'), 207 - _buildStatColumn('10.5M', 'Likes'), 82 + _buildStatColumn(context, '150', 'Following'), 83 + _buildStatColumn(context, '1.2M', 'Followers'), 84 + _buildStatColumn(context, '10.5M', 'Likes'), 208 85 ], 209 86 ), 210 87 ··· 219 96 padding: const EdgeInsets.symmetric(vertical: 12), 220 97 decoration: BoxDecoration( 221 98 border: Border.all( 222 - color: CupertinoColors.systemGrey4, 99 + color: isDarkMode ? AppColors.lightLavender : AppColors.deepPurple, 223 100 ), 224 101 borderRadius: BorderRadius.circular(4), 225 102 ), 226 - child: const Center( 103 + child: Center( 227 104 child: Text( 228 105 'Edit Profile', 229 106 style: TextStyle( 230 107 fontWeight: FontWeight.bold, 231 - color: CupertinoColors.label, 108 + color: AppTheme.getTextColor(context), 232 109 ), 233 110 ), 234 111 ), ··· 238 115 const SizedBox(height: 8), 239 116 240 117 // Bio (if any) 241 - const Text( 118 + Text( 242 119 'Digital creator | Making cool videos | For business inquiries: email@example.com', 243 120 textAlign: TextAlign.center, 244 121 style: TextStyle( 245 - color: CupertinoColors.systemGrey, 122 + color: AppTheme.getSecondaryTextColor(context), 246 123 ), 247 124 ), 248 125 ], 249 126 ), 250 127 ), 251 128 252 - // Tab bar 129 + // Tab selector 253 130 Container( 254 - decoration: const BoxDecoration( 131 + decoration: BoxDecoration( 255 132 border: Border( 133 + top: BorderSide( 134 + color: isDarkMode ? AppColors.darkPurple : AppColors.divider, 135 + width: 0.5, 136 + ), 256 137 bottom: BorderSide( 257 - color: CupertinoColors.systemGrey5, 138 + color: isDarkMode ? AppColors.darkPurple : AppColors.divider, 258 139 width: 0.5, 259 140 ), 260 141 ), 261 142 ), 262 143 child: Row( 263 144 children: [ 264 - Expanded( 265 - child: CupertinoButton( 266 - padding: EdgeInsets.zero, 267 - onPressed: () { 268 - setState(() { 269 - _selectedTabIndex = 0; 270 - }); 271 - }, 272 - child: Icon( 273 - Ionicons.grid_outline, 274 - color: _selectedTabIndex == 0 275 - ? CupertinoColors.activeBlue 276 - : CupertinoColors.systemGrey, 277 - ), 278 - ), 279 - ), 280 - Expanded( 281 - child: CupertinoButton( 282 - padding: EdgeInsets.zero, 283 - onPressed: () { 284 - setState(() { 285 - _selectedTabIndex = 1; 286 - }); 287 - }, 288 - child: Icon( 289 - Ionicons.heart_outline, 290 - color: _selectedTabIndex == 1 291 - ? CupertinoColors.activeBlue 292 - : CupertinoColors.systemGrey, 293 - ), 294 - ), 295 - ), 296 - Expanded( 297 - child: CupertinoButton( 298 - padding: EdgeInsets.zero, 299 - onPressed: () { 300 - setState(() { 301 - _selectedTabIndex = 2; 302 - }); 303 - }, 304 - child: Icon( 305 - Ionicons.lock_closed_outline, 306 - color: _selectedTabIndex == 2 307 - ? CupertinoColors.activeBlue 308 - : CupertinoColors.systemGrey, 309 - ), 310 - ), 311 - ), 145 + _buildTabButton(context, 0, Ionicons.grid_outline), 146 + _buildTabButton(context, 1, Ionicons.heart_outline), 147 + _buildTabButton(context, 2, Ionicons.lock_closed_outline), 312 148 ], 313 149 ), 314 150 ), 315 151 316 152 // Tab content 317 153 Expanded( 318 - child: _tabs[_selectedTabIndex], 154 + child: _buildTabContent(), 319 155 ), 320 156 ], 321 157 ), ··· 323 159 ); 324 160 } 325 161 326 - Widget _buildStatColumn(String count, String label) { 162 + Widget _buildStatColumn(BuildContext context, String count, String label) { 327 163 return Column( 328 164 children: [ 329 165 Text( 330 166 count, 331 - style: const TextStyle( 167 + style: TextStyle( 332 168 fontWeight: FontWeight.bold, 333 169 fontSize: 16, 170 + color: AppTheme.getTextColor(context), 334 171 ), 335 172 ), 336 173 const SizedBox(height: 4), 337 174 Text( 338 175 label, 339 - style: const TextStyle( 340 - color: CupertinoColors.systemGrey, 341 - fontSize: 12, 176 + style: TextStyle( 177 + color: AppTheme.getSecondaryTextColor(context), 342 178 ), 343 179 ), 344 180 ], 181 + ); 182 + } 183 + 184 + Widget _buildTabButton(BuildContext context, int index, IconData icon) { 185 + final brightness = MediaQuery.of(context).platformBrightness; 186 + final isDarkMode = brightness == Brightness.dark; 187 + final isSelected = _selectedTabIndex == index; 188 + 189 + return Expanded( 190 + child: CupertinoButton( 191 + padding: EdgeInsets.zero, 192 + onPressed: () { 193 + setState(() { 194 + _selectedTabIndex = index; 195 + }); 196 + }, 197 + child: Container( 198 + padding: const EdgeInsets.symmetric(vertical: 12), 199 + decoration: BoxDecoration( 200 + border: Border( 201 + bottom: BorderSide( 202 + color: isSelected 203 + ? AppColors.primary 204 + : Colors.transparent, 205 + width: 2, 206 + ), 207 + ), 208 + ), 209 + child: Icon( 210 + icon, 211 + color: isSelected 212 + ? AppColors.primary 213 + : (isDarkMode ? AppColors.textLight : AppColors.textSecondary), 214 + ), 215 + ), 216 + ), 217 + ); 218 + } 219 + 220 + Widget _buildTabContent() { 221 + final brightness = MediaQuery.of(context).platformBrightness; 222 + final isDarkMode = brightness == Brightness.dark; 223 + 224 + switch (_selectedTabIndex) { 225 + case 0: 226 + return _buildVideosGrid(); 227 + case 1: 228 + return _buildLikedGrid(); 229 + case 2: 230 + return _buildPrivateTab(); 231 + default: 232 + return const SizedBox.shrink(); 233 + } 234 + } 235 + 236 + Widget _buildVideosGrid() { 237 + return GridView.builder( 238 + padding: const EdgeInsets.all(1), 239 + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 240 + crossAxisCount: 3, 241 + childAspectRatio: 2/3, 242 + crossAxisSpacing: 1, 243 + mainAxisSpacing: 1, 244 + ), 245 + itemCount: 15, 246 + itemBuilder: (context, index) { 247 + return Container( 248 + color: index % 3 == 0 249 + ? AppColors.richPurple.withOpacity(0.7) 250 + : index % 3 == 1 251 + ? AppColors.brightPurple.withOpacity(0.7) 252 + : AppColors.primary.withOpacity(0.7), 253 + child: Stack( 254 + children: [ 255 + Center( 256 + child: Icon( 257 + Ionicons.play_outline, 258 + color: AppColors.white.withOpacity(0.8), 259 + ), 260 + ), 261 + Positioned( 262 + bottom: 5, 263 + left: 5, 264 + child: Row( 265 + children: [ 266 + const Icon( 267 + Ionicons.eye_outline, 268 + color: AppColors.white, 269 + size: 12, 270 + ), 271 + const SizedBox(width: 4), 272 + Text( 273 + '${(index + 1) * 1000}', 274 + style: const TextStyle( 275 + color: AppColors.white, 276 + fontSize: 12, 277 + ), 278 + ), 279 + ], 280 + ), 281 + ), 282 + ], 283 + ), 284 + ); 285 + }, 286 + ); 287 + } 288 + 289 + Widget _buildLikedGrid() { 290 + return GridView.builder( 291 + padding: const EdgeInsets.all(1), 292 + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 293 + crossAxisCount: 3, 294 + childAspectRatio: 2/3, 295 + crossAxisSpacing: 1, 296 + mainAxisSpacing: 1, 297 + ), 298 + itemCount: 9, 299 + itemBuilder: (context, index) { 300 + return Container( 301 + color: index % 3 == 0 302 + ? AppColors.orange.withOpacity(0.7) 303 + : index % 3 == 1 304 + ? AppColors.primary.withOpacity(0.7) 305 + : AppColors.red.withOpacity(0.7), 306 + child: Stack( 307 + children: [ 308 + Center( 309 + child: Icon( 310 + Ionicons.play_outline, 311 + color: AppColors.white.withOpacity(0.8), 312 + ), 313 + ), 314 + Positioned( 315 + bottom: 5, 316 + left: 5, 317 + child: Row( 318 + children: [ 319 + const Icon( 320 + Ionicons.heart_outline, 321 + color: AppColors.white, 322 + size: 12, 323 + ), 324 + const SizedBox(width: 4), 325 + Text( 326 + '${(index + 1) * 1000}', 327 + style: const TextStyle( 328 + color: AppColors.white, 329 + fontSize: 12, 330 + ), 331 + ), 332 + ], 333 + ), 334 + ), 335 + ], 336 + ), 337 + ); 338 + }, 339 + ); 340 + } 341 + 342 + Widget _buildPrivateTab() { 343 + return Center( 344 + child: Column( 345 + mainAxisAlignment: MainAxisAlignment.center, 346 + children: [ 347 + Icon( 348 + Ionicons.lock_closed_outline, 349 + size: 60, 350 + color: AppTheme.getSecondaryTextColor(context), 351 + ), 352 + const SizedBox(height: 20), 353 + Text( 354 + 'Private videos', 355 + style: TextStyle( 356 + fontWeight: FontWeight.bold, 357 + fontSize: 18, 358 + color: AppTheme.getTextColor(context), 359 + ), 360 + ), 361 + const SizedBox(height: 10), 362 + Text( 363 + 'Videos you\'ve saved to private will appear here', 364 + style: TextStyle( 365 + color: AppTheme.getSecondaryTextColor(context), 366 + ), 367 + textAlign: TextAlign.center, 368 + ), 369 + ], 370 + ), 345 371 ); 346 372 } 347 373 }
+35 -16
lib/screens/search_screen.dart
··· 1 1 import 'package:flutter/cupertino.dart'; 2 2 import 'package:ionicons/ionicons.dart'; 3 + import '../utils/app_colors.dart'; 4 + import '../utils/app_theme.dart'; 3 5 4 6 class SearchScreen extends StatefulWidget { 5 7 const SearchScreen({super.key}); ··· 19 21 20 22 @override 21 23 Widget build(BuildContext context) { 24 + final brightness = MediaQuery.of(context).platformBrightness; 25 + final isDarkMode = brightness == Brightness.dark; 26 + 22 27 return CupertinoPageScaffold( 23 - backgroundColor: CupertinoColors.systemBackground, 24 - navigationBar: const CupertinoNavigationBar( 25 - middle: Text('Discover'), 26 - backgroundColor: CupertinoColors.systemBackground, 28 + backgroundColor: AppTheme.getBackgroundColor(context, false), 29 + navigationBar: CupertinoNavigationBar( 30 + middle: Text( 31 + 'Discover', 32 + style: TextStyle(color: AppTheme.getTextColor(context)), 33 + ), 34 + backgroundColor: isDarkMode ? AppColors.deepPurple : AppColors.background, 27 35 ), 28 36 child: SafeArea( 29 37 child: Column( ··· 33 41 child: CupertinoSearchTextField( 34 42 controller: _searchController, 35 43 placeholder: 'Search videos, users, music', 36 - prefixIcon: const Icon(Ionicons.search_outline), 37 - suffixIcon: const Icon(Ionicons.scan_outline), 44 + prefixIcon: Icon( 45 + Ionicons.search_outline, 46 + color: AppTheme.getSecondaryTextColor(context), 47 + ), 48 + suffixIcon: Icon( 49 + Ionicons.scan_outline, 50 + color: AppTheme.getSecondaryTextColor(context), 51 + ), 52 + style: TextStyle(color: AppTheme.getTextColor(context)), 53 + placeholderStyle: TextStyle(color: AppTheme.getSecondaryTextColor(context)), 54 + backgroundColor: isDarkMode ? AppColors.deepPurple : AppColors.white, 38 55 onChanged: (value) { 39 56 // Handle search 40 57 }, ··· 42 59 ), 43 60 44 61 // Trending hashtags 45 - const Padding( 46 - padding: EdgeInsets.all(12.0), 62 + Padding( 63 + padding: const EdgeInsets.all(12.0), 47 64 child: Row( 48 65 children: [ 49 66 Text( ··· 51 68 style: TextStyle( 52 69 fontWeight: FontWeight.bold, 53 70 fontSize: 16, 71 + color: AppTheme.getTextColor(context), 54 72 ), 55 73 ), 56 74 ], ··· 69 87 margin: const EdgeInsets.only(right: 8), 70 88 padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), 71 89 decoration: BoxDecoration( 72 - color: CupertinoColors.systemGrey5, 90 + color: isDarkMode ? AppColors.darkPurple : AppColors.lightLavender, 73 91 borderRadius: BorderRadius.circular(20), 74 92 ), 75 93 child: Center( 76 94 child: Text( 77 95 '#trending${index + 1}', 78 - style: const TextStyle( 96 + style: TextStyle( 79 97 fontWeight: FontWeight.w500, 98 + color: AppTheme.getTextColor(context), 80 99 ), 81 100 ), 82 101 ), ··· 101 120 itemBuilder: (context, index) { 102 121 return Container( 103 122 color: index % 3 == 0 104 - ? CupertinoColors.systemPurple.withOpacity(0.7) 123 + ? AppColors.brightPurple.withOpacity(0.7) 105 124 : index % 3 == 1 106 - ? CupertinoColors.systemIndigo.withOpacity(0.7) 107 - : CupertinoColors.systemBlue.withOpacity(0.7), 125 + ? AppColors.richPurple.withOpacity(0.7) 126 + : AppColors.primary.withOpacity(0.7), 108 127 child: Stack( 109 128 fit: StackFit.expand, 110 129 children: [ 111 130 Center( 112 131 child: Icon( 113 132 Ionicons.play_outline, 114 - color: CupertinoColors.white.withOpacity(0.7), 133 + color: AppColors.white.withOpacity(0.7), 115 134 ), 116 135 ), 117 136 Positioned( ··· 121 140 children: [ 122 141 const Icon( 123 142 Ionicons.play_outline, 124 - color: CupertinoColors.white, 143 + color: AppColors.white, 125 144 size: 12, 126 145 ), 127 146 const SizedBox(width: 4), 128 147 Text( 129 148 '${(index + 1) * 10}K', 130 149 style: const TextStyle( 131 - color: CupertinoColors.white, 150 + color: AppColors.white, 132 151 fontSize: 12, 133 152 ), 134 153 ),
+11 -10
lib/screens/splash_screen.dart
··· 1 1 import 'dart:async'; 2 2 import 'package:flutter/cupertino.dart'; 3 + import '../utils/app_colors.dart'; 3 4 4 5 class SplashScreen extends StatefulWidget { 5 6 const SplashScreen({super.key}); ··· 54 55 width: 100, 55 56 height: 100, 56 57 decoration: BoxDecoration( 57 - gradient: const LinearGradient( 58 - colors: [ 59 - CupertinoColors.systemPink, 60 - CupertinoColors.systemBlue, 61 - ], 62 - begin: Alignment.bottomLeft, 63 - end: Alignment.topRight, 64 - ), 58 + color: AppColors.primary, 65 59 borderRadius: BorderRadius.circular(20), 60 + boxShadow: [ 61 + BoxShadow( 62 + color: AppColors.brightPurple.withOpacity(0.5), 63 + blurRadius: 15, 64 + spreadRadius: 5, 65 + ), 66 + ], 66 67 ), 67 68 child: const Center( 68 69 child: Text( 69 - 'TT', 70 + 'S', 70 71 style: TextStyle( 71 72 color: CupertinoColors.white, 72 73 fontSize: 40, ··· 77 78 ), 78 79 const SizedBox(height: 30), 79 80 const Text( 80 - 'TikTok Clone', 81 + 'Spark', 81 82 style: TextStyle( 82 83 color: CupertinoColors.white, 83 84 fontSize: 24,
+61
lib/utils/app_colors.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + 3 + class AppColors { 4 + // Main color palette 5 + static const Color lightLavender = Color(0xFFDCD9E2); 6 + static const Color darkPurple = Color(0xFF403848); 7 + static const Color deepPurple = Color(0xFF28232D); 8 + static const Color nearBlack = Color(0xFF0D0A0F); 9 + static const Color richPurple = Color(0xFF330072); 10 + static const Color brightPurple = Color(0xFFB20AFF); 11 + static const Color pink = Color(0xFFFF2696); // Main app color for buttons and highlights 12 + static const Color white = Color(0xFFFFFFFF); 13 + 14 + // New colors 15 + static const Color blue = Color(0xFF0073FF); // For followers 16 + static const Color green = Color(0xFF12DB59); // For comments 17 + static const Color red = Color(0xFFFF3A2C); // For likes 18 + static const Color orange = Color(0xFFFF7B00); // For alerts and danger 19 + 20 + // Semantic colors based on our palette 21 + static const Color primary = pink; 22 + static const Color secondary = brightPurple; 23 + static const Color accent = richPurple; 24 + 25 + // Background colors 26 + static const Color background = lightLavender; 27 + static const Color lightBackground = white; 28 + static const Color cardBackground = white; 29 + static const Color darkBackground = deepPurple; 30 + static const Color modalBackground = nearBlack; 31 + 32 + // Text colors 33 + static const Color textPrimary = deepPurple; 34 + static const Color textSecondary = darkPurple; 35 + static const Color textLight = lightLavender; 36 + static const Color textOnDark = white; 37 + 38 + // Border and divider colors 39 + static const Color border = darkPurple; 40 + static const Color divider = lightLavender; 41 + 42 + // Status colors 43 + static const Color success = green; 44 + static const Color error = red; 45 + static const Color warning = orange; 46 + static const Color info = blue; 47 + 48 + // Activity colors 49 + static const Color likeColor = red; 50 + static const Color commentColor = green; 51 + static const Color followColor = blue; 52 + 53 + // Unread indicator 54 + static const Color unreadIndicator = pink; 55 + 56 + // Navigation 57 + static const Color selectedIconLight = pink; 58 + static const Color unselectedIconLight = darkPurple; 59 + static const Color selectedIconDark = white; 60 + static const Color unselectedIconDark = darkPurple; 61 + }
+67
lib/utils/app_theme.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'app_colors.dart'; 3 + 4 + class AppTheme { 5 + // This theme will automatically adapt based on platform brightness 6 + static CupertinoThemeData get theme => const CupertinoThemeData( 7 + brightness: Brightness.light, // Default brightness, will be overridden by system 8 + primaryColor: AppColors.primary, 9 + primaryContrastingColor: AppColors.white, 10 + barBackgroundColor: AppColors.background, 11 + scaffoldBackgroundColor: AppColors.background, 12 + ); 13 + 14 + // Helper methods to determine colors based on theme brightness 15 + static Color getNavBackgroundColor(BuildContext context, bool isHomePage) { 16 + final brightness = MediaQuery.of(context).platformBrightness; 17 + if (isHomePage) { 18 + return AppColors.nearBlack; 19 + } 20 + 21 + return brightness == Brightness.dark 22 + ? AppColors.deepPurple 23 + : AppColors.lightBackground; 24 + } 25 + 26 + static Color getSelectedIconColor(BuildContext context, bool isHomePage) { 27 + final brightness = MediaQuery.of(context).platformBrightness; 28 + if (isHomePage) { 29 + return AppColors.selectedIconDark; 30 + } 31 + 32 + return brightness == Brightness.dark 33 + ? AppColors.selectedIconDark 34 + : AppColors.selectedIconLight; 35 + } 36 + 37 + static Color getUnselectedIconColor(BuildContext context, bool isHomePage) { 38 + return AppColors.unselectedIconDark; 39 + } 40 + 41 + static Color getBackgroundColor(BuildContext context, bool isHomePage) { 42 + final brightness = MediaQuery.of(context).platformBrightness; 43 + if (isHomePage) { 44 + return AppColors.nearBlack; 45 + } 46 + 47 + return brightness == Brightness.dark 48 + ? AppColors.darkBackground 49 + : AppColors.background; 50 + } 51 + 52 + // Get text color based on brightness 53 + static Color getTextColor(BuildContext context) { 54 + final brightness = MediaQuery.of(context).platformBrightness; 55 + return brightness == Brightness.dark 56 + ? AppColors.textLight 57 + : AppColors.textPrimary; 58 + } 59 + 60 + // Get secondary text color based on brightness 61 + static Color getSecondaryTextColor(BuildContext context) { 62 + final brightness = MediaQuery.of(context).platformBrightness; 63 + return brightness == Brightness.dark 64 + ? AppColors.textLight.withOpacity(0.7) 65 + : AppColors.textSecondary; 66 + } 67 + }
+153
lib/widgets/activities/activity_content.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'activity_icon.dart'; 3 + import '../../utils/app_colors.dart'; 4 + import '../../utils/app_theme.dart'; 5 + 6 + class ActivityContent extends StatelessWidget { 7 + final String username; 8 + final ActivityType type; 9 + final String time; 10 + final String? additionalInfo; 11 + final bool isDarkMode; 12 + 13 + const ActivityContent({ 14 + super.key, 15 + required this.username, 16 + required this.type, 17 + required this.time, 18 + this.additionalInfo, 19 + this.isDarkMode = false, 20 + }); 21 + 22 + @override 23 + Widget build(BuildContext context) { 24 + return Column( 25 + crossAxisAlignment: CrossAxisAlignment.start, 26 + children: [ 27 + // Activity description and time 28 + Row( 29 + mainAxisAlignment: MainAxisAlignment.spaceBetween, 30 + children: [ 31 + Expanded( 32 + child: ActivityDescription( 33 + username: username, 34 + type: type, 35 + isDarkMode: isDarkMode, 36 + ), 37 + ), 38 + ActivityTime( 39 + time: time, 40 + isDarkMode: isDarkMode, 41 + ), 42 + ], 43 + ), 44 + 45 + // Optional additional info 46 + if (additionalInfo != null) ...[ 47 + const SizedBox(height: 4), 48 + AdditionalInfoText( 49 + text: additionalInfo!, 50 + isDarkMode: isDarkMode, 51 + ), 52 + ], 53 + ], 54 + ); 55 + } 56 + } 57 + 58 + class ActivityDescription extends StatelessWidget { 59 + final String username; 60 + final ActivityType type; 61 + final bool isDarkMode; 62 + 63 + const ActivityDescription({ 64 + super.key, 65 + required this.username, 66 + required this.type, 67 + this.isDarkMode = false, 68 + }); 69 + 70 + @override 71 + Widget build(BuildContext context) { 72 + final String actionText = _getActionText(type); 73 + 74 + return RichText( 75 + maxLines: 1, 76 + overflow: TextOverflow.ellipsis, 77 + text: TextSpan( 78 + style: TextStyle( 79 + fontSize: 16, 80 + color: AppTheme.getTextColor(context), 81 + ), 82 + children: [ 83 + TextSpan( 84 + text: username, 85 + style: const TextStyle( 86 + fontWeight: FontWeight.bold, 87 + ), 88 + ), 89 + TextSpan( 90 + text: ' $actionText', 91 + ), 92 + ], 93 + ), 94 + ); 95 + } 96 + 97 + String _getActionText(ActivityType type) { 98 + switch (type) { 99 + case ActivityType.like: 100 + return 'liked your post'; 101 + case ActivityType.comment: 102 + return 'commented on your post'; 103 + case ActivityType.follow: 104 + return 'started following you'; 105 + } 106 + } 107 + } 108 + 109 + class ActivityTime extends StatelessWidget { 110 + final String time; 111 + final bool isDarkMode; 112 + 113 + const ActivityTime({ 114 + super.key, 115 + required this.time, 116 + this.isDarkMode = false, 117 + }); 118 + 119 + @override 120 + Widget build(BuildContext context) { 121 + return Text( 122 + time, 123 + style: TextStyle( 124 + color: AppTheme.getSecondaryTextColor(context), 125 + fontSize: 12, 126 + ), 127 + ); 128 + } 129 + } 130 + 131 + class AdditionalInfoText extends StatelessWidget { 132 + final String text; 133 + final bool isDarkMode; 134 + 135 + const AdditionalInfoText({ 136 + super.key, 137 + required this.text, 138 + this.isDarkMode = false, 139 + }); 140 + 141 + @override 142 + Widget build(BuildContext context) { 143 + return Text( 144 + text, 145 + style: TextStyle( 146 + color: AppTheme.getSecondaryTextColor(context), 147 + fontSize: 14, 148 + ), 149 + maxLines: 1, 150 + overflow: TextOverflow.ellipsis, 151 + ); 152 + } 153 + }
+62
lib/widgets/activities/activity_icon.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'package:ionicons/ionicons.dart'; 3 + import '../../utils/app_colors.dart'; 4 + 5 + enum ActivityType { 6 + like, 7 + comment, 8 + follow, 9 + } 10 + 11 + class ActivityIcon extends StatelessWidget { 12 + final ActivityType type; 13 + final double size; 14 + 15 + const ActivityIcon({ 16 + super.key, 17 + required this.type, 18 + this.size = 40, 19 + }); 20 + 21 + @override 22 + Widget build(BuildContext context) { 23 + // Determine icon and color based on activity type 24 + IconData iconData; 25 + Color backgroundColor; 26 + Color iconColor; 27 + 28 + switch (type) { 29 + case ActivityType.like: 30 + iconData = Ionicons.heart; 31 + backgroundColor = AppColors.likeColor.withOpacity(0.2); 32 + iconColor = AppColors.likeColor; 33 + break; 34 + case ActivityType.comment: 35 + iconData = Ionicons.chatbubble; 36 + backgroundColor = AppColors.commentColor.withOpacity(0.2); 37 + iconColor = AppColors.commentColor; 38 + break; 39 + case ActivityType.follow: 40 + iconData = Ionicons.person_add; 41 + backgroundColor = AppColors.followColor.withOpacity(0.2); 42 + iconColor = AppColors.followColor; 43 + break; 44 + } 45 + 46 + return Container( 47 + width: size, 48 + height: size, 49 + decoration: BoxDecoration( 50 + color: backgroundColor, 51 + shape: BoxShape.circle, 52 + ), 53 + child: Center( 54 + child: Icon( 55 + iconData, 56 + color: iconColor, 57 + size: size * 0.5, 58 + ), 59 + ), 60 + ); 61 + } 62 + }
+53
lib/widgets/activities/activity_list.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'activity_list_item.dart'; 3 + import 'activity_icon.dart'; 4 + 5 + class ActivityList extends StatelessWidget { 6 + final List<ActivityData> activities; 7 + final Function(ActivityData)? onActivityTap; 8 + 9 + const ActivityList({ 10 + super.key, 11 + required this.activities, 12 + this.onActivityTap, 13 + }); 14 + 15 + @override 16 + Widget build(BuildContext context) { 17 + return ListView.builder( 18 + itemCount: activities.length, 19 + itemBuilder: (context, index) { 20 + final ActivityData activity = activities[index]; 21 + return ActivityListItem( 22 + username: activity.username, 23 + type: activity.type, 24 + time: activity.timeString, 25 + additionalInfo: activity.additionalInfo, 26 + onTap: () { 27 + if (onActivityTap != null) { 28 + onActivityTap!(activity); 29 + } 30 + }, 31 + ); 32 + }, 33 + ); 34 + } 35 + } 36 + 37 + class ActivityData { 38 + final String id; 39 + final String username; 40 + final ActivityType type; 41 + final String timeString; 42 + final String? additionalInfo; 43 + final String? targetContentId; 44 + 45 + ActivityData({ 46 + required this.id, 47 + required this.username, 48 + required this.type, 49 + required this.timeString, 50 + this.additionalInfo, 51 + this.targetContentId, 52 + }); 53 + }
+73
lib/widgets/activities/activity_list_item.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'package:ionicons/ionicons.dart'; 3 + import 'activity_icon.dart'; 4 + import 'activity_content.dart'; 5 + import '../../utils/app_colors.dart'; 6 + import '../../utils/app_theme.dart'; 7 + 8 + class ActivityListItem extends StatelessWidget { 9 + final String username; 10 + final ActivityType type; 11 + final String time; 12 + final String? additionalInfo; 13 + final VoidCallback? onTap; 14 + 15 + const ActivityListItem({ 16 + super.key, 17 + required this.username, 18 + required this.type, 19 + required this.time, 20 + this.additionalInfo, 21 + this.onTap, 22 + }); 23 + 24 + @override 25 + Widget build(BuildContext context) { 26 + final brightness = MediaQuery.of(context).platformBrightness; 27 + final isDarkMode = brightness == Brightness.dark; 28 + 29 + return GestureDetector( 30 + onTap: onTap, 31 + child: Container( 32 + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 33 + decoration: BoxDecoration( 34 + color: isDarkMode ? AppColors.deepPurple : AppColors.white, 35 + border: Border( 36 + bottom: BorderSide( 37 + color: isDarkMode ? AppColors.darkPurple : AppColors.divider, 38 + width: 0.5, 39 + ), 40 + ), 41 + ), 42 + child: Row( 43 + children: [ 44 + // Activity icon 45 + ActivityIcon(type: type), 46 + 47 + const SizedBox(width: 12), 48 + 49 + // Activity content 50 + Expanded( 51 + child: ActivityContent( 52 + username: username, 53 + type: type, 54 + time: time, 55 + additionalInfo: additionalInfo, 56 + isDarkMode: isDarkMode, 57 + ), 58 + ), 59 + 60 + const SizedBox(width: 8), 61 + 62 + // Chevron 63 + Icon( 64 + Ionicons.chevron_forward, 65 + color: AppTheme.getSecondaryTextColor(context), 66 + size: 16, 67 + ), 68 + ], 69 + ), 70 + ), 71 + ); 72 + } 73 + }
+53
lib/widgets/messages/message_list.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'message_list_item.dart'; 3 + 4 + class MessageList extends StatelessWidget { 5 + final List<MessageData> messages; 6 + final Function(MessageData)? onMessageTap; 7 + 8 + const MessageList({ 9 + super.key, 10 + required this.messages, 11 + this.onMessageTap, 12 + }); 13 + 14 + @override 15 + Widget build(BuildContext context) { 16 + return ListView.builder( 17 + itemCount: messages.length, 18 + itemBuilder: (context, index) { 19 + final MessageData message = messages[index]; 20 + return MessageListItem( 21 + username: message.username, 22 + message: message.messagePreview, 23 + time: message.timeString, 24 + unreadCount: message.unreadCount, 25 + colorIndex: index, 26 + onTap: () { 27 + if (onMessageTap != null) { 28 + onMessageTap!(message); 29 + } 30 + }, 31 + ); 32 + }, 33 + ); 34 + } 35 + } 36 + 37 + class MessageData { 38 + final String username; 39 + final String messagePreview; 40 + final String timeString; 41 + final int? unreadCount; 42 + final String? avatarUrl; 43 + final String id; 44 + 45 + MessageData({ 46 + required this.username, 47 + required this.messagePreview, 48 + required this.timeString, 49 + this.unreadCount, 50 + this.avatarUrl, 51 + required this.id, 52 + }); 53 + }
+80
lib/widgets/messages/message_list_item.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'package:ionicons/ionicons.dart'; 3 + import 'user_profile_picture.dart'; 4 + import 'message_preview.dart'; 5 + import '../../utils/app_colors.dart'; 6 + import '../../utils/app_theme.dart'; 7 + 8 + class MessageListItem extends StatelessWidget { 9 + final String username; 10 + final String message; 11 + final String time; 12 + final int? unreadCount; 13 + final int colorIndex; 14 + final VoidCallback? onTap; 15 + 16 + const MessageListItem({ 17 + super.key, 18 + required this.username, 19 + required this.message, 20 + required this.time, 21 + this.unreadCount, 22 + required this.colorIndex, 23 + this.onTap, 24 + }); 25 + 26 + @override 27 + Widget build(BuildContext context) { 28 + final bool hasUnread = unreadCount != null && unreadCount! > 0; 29 + final brightness = MediaQuery.of(context).platformBrightness; 30 + final isDarkMode = brightness == Brightness.dark; 31 + 32 + return GestureDetector( 33 + onTap: onTap, 34 + child: Container( 35 + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 36 + decoration: BoxDecoration( 37 + color: isDarkMode ? AppColors.deepPurple : AppColors.white, 38 + border: Border( 39 + bottom: BorderSide( 40 + color: isDarkMode ? AppColors.darkPurple : AppColors.divider, 41 + width: 0.5, 42 + ), 43 + ), 44 + ), 45 + child: Row( 46 + children: [ 47 + // Profile image with unread indicator 48 + UserProfilePicture( 49 + colorIndex: colorIndex, 50 + unreadCount: unreadCount, 51 + onTap: onTap, 52 + ), 53 + 54 + const SizedBox(width: 12), 55 + 56 + // Message content 57 + Expanded( 58 + child: MessagePreview( 59 + username: username, 60 + message: message, 61 + time: time, 62 + isUnread: hasUnread, 63 + isDarkMode: isDarkMode, 64 + ), 65 + ), 66 + 67 + const SizedBox(width: 8), 68 + 69 + // Right chevron 70 + Icon( 71 + Ionicons.chevron_forward, 72 + color: AppTheme.getSecondaryTextColor(context), 73 + size: 16, 74 + ), 75 + ], 76 + ), 77 + ), 78 + ); 79 + } 80 + }
+131
lib/widgets/messages/message_preview.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import '../../utils/app_colors.dart'; 3 + import '../../utils/app_theme.dart'; 4 + 5 + class MessagePreview extends StatelessWidget { 6 + final String username; 7 + final String message; 8 + final String time; 9 + final bool isUnread; 10 + final bool isDarkMode; 11 + 12 + const MessagePreview({ 13 + super.key, 14 + required this.username, 15 + required this.message, 16 + required this.time, 17 + this.isUnread = false, 18 + this.isDarkMode = false, 19 + }); 20 + 21 + @override 22 + Widget build(BuildContext context) { 23 + return Column( 24 + crossAxisAlignment: CrossAxisAlignment.start, 25 + children: [ 26 + // Username and time row 27 + Row( 28 + mainAxisAlignment: MainAxisAlignment.spaceBetween, 29 + children: [ 30 + UsernameText( 31 + username: username, 32 + isBold: isUnread, 33 + isDarkMode: isDarkMode, 34 + ), 35 + TimeText( 36 + time: time, 37 + isHighlighted: isUnread, 38 + isDarkMode: isDarkMode, 39 + ), 40 + ], 41 + ), 42 + const SizedBox(height: 4), 43 + // Message preview text 44 + MessageText( 45 + message: message, 46 + isUnread: isUnread, 47 + isDarkMode: isDarkMode, 48 + ), 49 + ], 50 + ); 51 + } 52 + } 53 + 54 + class UsernameText extends StatelessWidget { 55 + final String username; 56 + final bool isBold; 57 + final bool isDarkMode; 58 + 59 + const UsernameText({ 60 + super.key, 61 + required this.username, 62 + this.isBold = false, 63 + this.isDarkMode = false, 64 + }); 65 + 66 + @override 67 + Widget build(BuildContext context) { 68 + return Text( 69 + username, 70 + style: TextStyle( 71 + color: AppTheme.getTextColor(context), 72 + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, 73 + fontSize: 16, 74 + ), 75 + ); 76 + } 77 + } 78 + 79 + class TimeText extends StatelessWidget { 80 + final String time; 81 + final bool isHighlighted; 82 + final bool isDarkMode; 83 + 84 + const TimeText({ 85 + super.key, 86 + required this.time, 87 + this.isHighlighted = false, 88 + this.isDarkMode = false, 89 + }); 90 + 91 + @override 92 + Widget build(BuildContext context) { 93 + return Text( 94 + time, 95 + style: TextStyle( 96 + color: isHighlighted 97 + ? AppColors.primary 98 + : AppTheme.getSecondaryTextColor(context), 99 + fontSize: 12, 100 + ), 101 + ); 102 + } 103 + } 104 + 105 + class MessageText extends StatelessWidget { 106 + final String message; 107 + final bool isUnread; 108 + final bool isDarkMode; 109 + 110 + const MessageText({ 111 + super.key, 112 + required this.message, 113 + this.isUnread = false, 114 + this.isDarkMode = false, 115 + }); 116 + 117 + @override 118 + Widget build(BuildContext context) { 119 + return Text( 120 + message, 121 + style: TextStyle( 122 + color: isUnread 123 + ? AppTheme.getTextColor(context) 124 + : AppTheme.getSecondaryTextColor(context), 125 + fontWeight: isUnread ? FontWeight.w500 : FontWeight.normal, 126 + ), 127 + maxLines: 1, 128 + overflow: TextOverflow.ellipsis, 129 + ); 130 + } 131 + }
+92
lib/widgets/messages/user_profile_picture.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'package:ionicons/ionicons.dart'; 3 + import '../../utils/app_colors.dart'; 4 + 5 + class UserProfilePicture extends StatelessWidget { 6 + final int colorIndex; 7 + final int? unreadCount; 8 + final double size; 9 + final VoidCallback? onTap; 10 + 11 + const UserProfilePicture({ 12 + super.key, 13 + required this.colorIndex, 14 + this.unreadCount, 15 + this.size = 60, 16 + this.onTap, 17 + }); 18 + 19 + @override 20 + Widget build(BuildContext context) { 21 + final bool hasUnread = unreadCount != null && unreadCount! > 0; 22 + 23 + return GestureDetector( 24 + onTap: onTap, 25 + child: Stack( 26 + children: [ 27 + // Profile image container 28 + Container( 29 + width: size, 30 + height: size, 31 + decoration: BoxDecoration( 32 + color: colorIndex % 2 == 0 33 + ? AppColors.brightPurple.withOpacity(0.2) 34 + : AppColors.richPurple.withOpacity(0.2), 35 + shape: BoxShape.circle, 36 + ), 37 + child: Center( 38 + child: Icon( 39 + Ionicons.person_outline, 40 + color: colorIndex % 2 == 0 41 + ? AppColors.brightPurple 42 + : AppColors.richPurple, 43 + size: size * 0.5, 44 + ), 45 + ), 46 + ), 47 + 48 + // Unread indicator 49 + if (hasUnread) 50 + Positioned( 51 + top: 0, 52 + right: 0, 53 + child: UnreadIndicator(count: unreadCount!), 54 + ), 55 + ], 56 + ), 57 + ); 58 + } 59 + } 60 + 61 + class UnreadIndicator extends StatelessWidget { 62 + final int count; 63 + final double size; 64 + 65 + const UnreadIndicator({ 66 + super.key, 67 + required this.count, 68 + this.size = 16, 69 + }); 70 + 71 + @override 72 + Widget build(BuildContext context) { 73 + return Container( 74 + width: size, 75 + height: size, 76 + decoration: const BoxDecoration( 77 + color: AppColors.unreadIndicator, 78 + shape: BoxShape.circle, 79 + ), 80 + child: Center( 81 + child: Text( 82 + count > 9 ? '9+' : count.toString(), 83 + style: TextStyle( 84 + color: AppColors.white, 85 + fontSize: size * 0.625, 86 + fontWeight: FontWeight.bold, 87 + ), 88 + ), 89 + ), 90 + ); 91 + } 92 + }
+88
pubspec.lock
··· 1 1 # Generated by pub 2 2 # See https://dart.dev/tools/pub/glossary#lockfile 3 3 packages: 4 + archive: 5 + dependency: transitive 6 + description: 7 + name: archive 8 + sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" 9 + url: "https://pub.dev" 10 + source: hosted 11 + version: "4.0.4" 12 + args: 13 + dependency: transitive 14 + description: 15 + name: args 16 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 17 + url: "https://pub.dev" 18 + source: hosted 19 + version: "2.7.0" 4 20 async: 5 21 dependency: transitive 6 22 description: ··· 89 105 url: "https://pub.dev" 90 106 source: hosted 91 107 version: "1.4.0" 108 + checked_yaml: 109 + dependency: transitive 110 + description: 111 + name: checked_yaml 112 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 113 + url: "https://pub.dev" 114 + source: hosted 115 + version: "2.0.3" 116 + cli_util: 117 + dependency: transitive 118 + description: 119 + name: cli_util 120 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c 121 + url: "https://pub.dev" 122 + source: hosted 123 + version: "0.4.2" 92 124 clock: 93 125 dependency: transitive 94 126 description: ··· 182 214 url: "https://pub.dev" 183 215 source: hosted 184 216 version: "3.4.1" 217 + flutter_launcher_icons: 218 + dependency: "direct main" 219 + description: 220 + name: flutter_launcher_icons 221 + sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c 222 + url: "https://pub.dev" 223 + source: hosted 224 + version: "0.14.3" 185 225 flutter_lints: 186 226 dependency: "direct dev" 187 227 description: ··· 232 272 url: "https://pub.dev" 233 273 source: hosted 234 274 version: "4.1.2" 275 + image: 276 + dependency: transitive 277 + description: 278 + name: image 279 + sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" 280 + url: "https://pub.dev" 281 + source: hosted 282 + version: "4.5.3" 235 283 ionicons: 236 284 dependency: "direct main" 237 285 description: ··· 240 288 url: "https://pub.dev" 241 289 source: hosted 242 290 version: "0.2.2" 291 + json_annotation: 292 + dependency: transitive 293 + description: 294 + name: json_annotation 295 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 296 + url: "https://pub.dev" 297 + source: hosted 298 + version: "4.9.0" 243 299 leak_tracker: 244 300 dependency: transitive 245 301 description: ··· 368 424 url: "https://pub.dev" 369 425 source: hosted 370 426 version: "2.3.0" 427 + petitparser: 428 + dependency: transitive 429 + description: 430 + name: petitparser 431 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" 432 + url: "https://pub.dev" 433 + source: hosted 434 + version: "6.1.0" 371 435 platform: 372 436 dependency: transitive 373 437 description: ··· 384 448 url: "https://pub.dev" 385 449 source: hosted 386 450 version: "2.1.8" 451 + posix: 452 + dependency: transitive 453 + description: 454 + name: posix 455 + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a 456 + url: "https://pub.dev" 457 + source: hosted 458 + version: "6.0.1" 387 459 provider: 388 460 dependency: "direct main" 389 461 description: ··· 605 677 url: "https://pub.dev" 606 678 source: hosted 607 679 version: "1.1.0" 680 + xml: 681 + dependency: transitive 682 + description: 683 + name: xml 684 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 685 + url: "https://pub.dev" 686 + source: hosted 687 + version: "6.5.0" 688 + yaml: 689 + dependency: transitive 690 + description: 691 + name: yaml 692 + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 693 + url: "https://pub.dev" 694 + source: hosted 695 + version: "3.1.3" 608 696 sdks: 609 697 dart: ">=3.7.2 <4.0.0" 610 698 flutter: ">=3.27.0"
+9
pubspec.yaml
··· 16 16 camera: ^0.10.5+9 17 17 path_provider: ^2.1.2 18 18 provider: ^6.1.1 19 + flutter_launcher_icons: ^0.14.3 19 20 20 21 dev_dependencies: 21 22 flutter_test: 22 23 sdk: flutter 23 24 flutter_lints: ^5.0.0 25 + 26 + flutter_launcher_icons: 27 + android: true 28 + ios: true 29 + image_path: "assets/images/icon.png" 30 + min_sdk_android: 21 31 + adaptive_icon_background: "#FFFFFF" 32 + adaptive_icon_foreground: "assets/images/icon.png" 24 33 25 34 flutter: 26 35 uses-material-design: true