diff --git a/.env b/.env index 9a56f2d..32046db 100644 --- a/.env +++ b/.env @@ -1,4 +1,6 @@ -API_URL=https://avah-pollinical-randal.ngrok-free.dev/ +# API_URL=https://app-dbef67eb-9a2e-44fa-abff-3e8b83204d9c.cleverapps.io/ +# API_URL=https://node.shoy.publicvm.com/ +API_URL=https://node.shoy.publicvm.com/ # API_test_URL=https://example.com/ # API_URL=https://wanita-hypernormal-cherise.ngrok-free.dev/ # API_URL=https://avah-pollinical-randal.ngrok-free.dev/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 1f09463..ffafe51 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "cmake.sourceDirectory": "D:/ThirdYear/SWE/project/Cross_Platform/linux" + "cmake.sourceDirectory": "C:/Users/kerol/Cross_Platform/linux" } \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e792664..aedeb3f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,9 +1,11 @@ +import java.util.Properties +import java.io.FileInputStream + plugins { id("com.android.application") id("kotlin-android") id("dev.flutter.flutter-gradle-plugin") id("com.google.gms.google-services") - } android { @@ -11,6 +13,28 @@ android { compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion + signingConfigs { + getByName("debug") { + storeFile = file("debug.keystore") + storePassword = "android" + keyAlias = "androiddebugkey" + keyPassword = "android" + } + + create("release") { + val keystoreProperties = Properties() + val keystorePropertiesFile = rootProject.file("key.properties") + if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) + } + + storeFile = file(keystoreProperties["storeFile"] as String) + storePassword = keystoreProperties["storePassword"] as String + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + } + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -21,32 +45,33 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.Artemsia.lite_x" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") - } +buildTypes { + getByName("debug") { + signingConfig = signingConfigs.getByName("debug") + } + getByName("release") { + signingConfig = signingConfigs.getByName("release") + isMinifyEnabled = false + isShrinkResources = false } } + +} + dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation(platform("com.google.firebase:firebase-bom:33.5.1")) implementation("com.google.firebase:firebase-messaging") } - flutter { source = "../.." -} +} \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json index 82e92bf..926b671 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,41 +1,34 @@ { "project_info": { - "project_number": "123824690535", - "project_id": "litex-3c6f1", - "storage_bucket": "litex-3c6f1.firebasestorage.app" + "project_number": "112144721859", + "project_id": "psychic-fin-474008-h8", + "storage_bucket": "psychic-fin-474008-h8.firebasestorage.app" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:123824690535:android:f0760abf0029e802960bc2", + "mobilesdk_app_id": "1:112144721859:android:227c69fccfe2ec4c813f76", "android_client_info": { "package_name": "com.Artemsia.lite_x" } }, "oauth_client": [ { - "client_id": "123824690535-52cesp7okt1d8j63pn7su9rtmi0asq0b.apps.googleusercontent.com", + "client_id": "112144721859-3i16bpjr6jd704h3imdsp4ojodv7t64l.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { - "current_key": "AIzaSyDYiU34-5-Rr2nP_SHphvLSIiOyr4RuC8I" + "current_key": "AIzaSyAPqcctNBpWkQ-BKvYsHUEjvc_iwPBwsZ0" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [ { - "client_id": "123824690535-52cesp7okt1d8j63pn7su9rtmi0asq0b.apps.googleusercontent.com", + "client_id": "112144721859-3i16bpjr6jd704h3imdsp4ojodv7t64l.apps.googleusercontent.com", "client_type": 3 - }, - { - "client_id": "123824690535-5pta8j3mu07g0bb21n43n8q2vuulrstr.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "com.example.litex" - } } ] } diff --git a/lib/features/home/view/widgets/home_app_bar.dart b/lib/features/home/view/widgets/home_app_bar.dart index edc264e..d33d8e3 100644 --- a/lib/features/home/view/widgets/home_app_bar.dart +++ b/lib/features/home/view/widgets/home_app_bar.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/legacy.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:lite_x/core/providers/current_user_provider.dart'; import 'package:lite_x/features/home/view/widgets/home_tab_bar.dart'; import 'package:lite_x/features/home/view/widgets/profile_avatar.dart'; diff --git a/lib/features/profile/models/profile_tweet_model.dart b/lib/features/profile/models/profile_tweet_model.dart index c1fe264..582a4ba 100644 --- a/lib/features/profile/models/profile_tweet_model.dart +++ b/lib/features/profile/models/profile_tweet_model.dart @@ -22,9 +22,6 @@ class ProfileTweetModel { final TweetType type; final List mediaIds; final String parentId; - final String retweeterName; - final String retweeterUserName; - final List> hashtags; ProfileTweetModel({ required this.id, @@ -48,9 +45,6 @@ class ProfileTweetModel { required this.type, required this.mediaIds, required this.parentId, - required this.retweeterName, - required this.retweeterUserName, - required this.hashtags, }); factory ProfileTweetModel.fromJson(Map json) { @@ -68,15 +62,6 @@ class ProfileTweetModel { // final List meidaIds = mediaIdsRes.map((json) { // return json["mediaId"] as String; // }).toList(); - final hashtagsList = json["hashtags"]; - final List> hashtagsModelList = hashtagsList - .map((h) { - final String id = h["hash"]?["id"] ?? ""; - final String tag_name = h["hash"]?["tag_text"] ?? ""; - return ({"hashtagName": tag_name, "id": id}); - }) - .toList() - .cast>(); return ProfileTweetModel( id: json['id'] ?? "", text: json['content'] ?? "", @@ -98,9 +83,7 @@ class ProfileTweetModel { protectedAccount: json["user"]?["protectedAccount"] ?? false, verified: json["user"]?["verified"] ?? false, parentId: json["parentId"] ?? "", - retweeterName: json["retweeter"]?["name"] ?? "", - retweeterUserName: json["retweeter"]?["username"] ?? "", - hashtags: hashtagsModelList, + // mediaIds: meidaIds, ); } // ------------------------------------------ diff --git a/lib/features/profile/models/shared.dart b/lib/features/profile/models/shared.dart index ab7a157..bf3d42e 100644 --- a/lib/features/profile/models/shared.dart +++ b/lib/features/profile/models/shared.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:lite_x/core/providers/current_user_provider.dart'; -import 'package:lite_x/features/home/models/tweet_model.dart'; +import 'package:lite_x/features/media/download_media.dart'; import 'package:lite_x/features/media/view_model/providers.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; @@ -84,7 +84,7 @@ Future showUnFollowDialog( }, ); - return result != null ? result : false; + return result; } enum EditProfileStatus { changedSuccessfully, unChanged, failedToChange } @@ -210,7 +210,7 @@ ProfileTweet getCorrectTweetType( ) { TweetType type = tweetModel.type; - if (tweetModel.retweeterUserName.isNotEmpty) + if (type == TweetType.ReTweet) return ProfileRetweetWidget(profileModel: pm, tweetModel: tweetModel); if (type == TweetType.Quote) @@ -394,7 +394,7 @@ class _BuildSmallProfileImageState } const String unkownUserAvatar = - "https://img.myloview.com/posters/default-avatar-profile-flat-icon-social-media-user-vector-portrait-of-unknown-a-human-image-700-209987471.jpg"; + "https://icon-library.com/images/unknown-person-icon/unknown-person-icon-4.jpg"; class InterActionsRowOfTweet extends ConsumerStatefulWidget { const InterActionsRowOfTweet({super.key, required this.tweet}); @@ -465,13 +465,12 @@ class _InterActionsRowOfTweetState retweet(widget.tweet.id).then((res) { res.fold( (l) { - if (mounted) - showSmallPopUpMessage( - context: context, - message: l.message, - borderColor: Colors.red, - icon: Icon(Icons.error, color: Colors.red), - ); + showSmallPopUpMessage( + context: context, + message: l.message, + borderColor: Colors.red, + icon: Icon(Icons.error, color: Colors.red), + ); }, (r) { if (mounted) @@ -510,13 +509,12 @@ class _InterActionsRowOfTweetState unretweet(widget.tweet.id).then((res) { res.fold( (l) { - if (mounted) - showSmallPopUpMessage( - context: context, - message: l.message, - borderColor: Colors.red, - icon: Icon(Icons.error, color: Colors.red), - ); + showSmallPopUpMessage( + context: context, + message: l.message, + borderColor: Colors.red, + icon: Icon(Icons.error, color: Colors.red), + ); }, (r) { if (mounted) @@ -584,23 +582,21 @@ class _InterActionsRowOfTweetState (l) { isLikedByMeLocal = true; likesCount += 1; - if (mounted) - showSmallPopUpMessage( - context: context, - message: l.message, - borderColor: Colors.red, - icon: Icon(Icons.error, color: Colors.red), - ); + showSmallPopUpMessage( + context: context, + message: l.message, + borderColor: Colors.red, + icon: Icon(Icons.error, color: Colors.red), + ); if (mounted) setState(() {}); }, (r) { - // ref.refresh( - // profilePostsProvider(widget.tweet.userUserName), - // ); - // final currUser = ref.watch(currentUserProvider); - // if (currUser != null) { - // ref.refresh(profilePostsProvider(currUser.username)); - // } + ref.refresh( + profilePostsProvider(widget.tweet.userUserName), + ); + final currUser = ref.watch(currentUserProvider); + if (currUser != null) + ref.refresh(profilePostsProvider(currUser.username)); }, ); }); @@ -611,13 +607,12 @@ class _InterActionsRowOfTweetState (l) { isLikedByMeLocal = false; likesCount -= 1; - if (mounted) - showSmallPopUpMessage( - context: context, - message: l.message, - borderColor: Colors.red, - icon: Icon(Icons.error, color: Colors.red), - ); + showSmallPopUpMessage( + context: context, + message: l.message, + borderColor: Colors.red, + icon: Icon(Icons.error, color: Colors.red), + ); if (mounted) setState(() {}); }, (r) { @@ -626,9 +621,8 @@ class _InterActionsRowOfTweetState profilePostsProvider(widget.tweet.userUserName), ); final currUser = ref.watch(currentUserProvider); - if (currUser != null) { + if (currUser != null) ref.refresh(profilePostsProvider(currUser.username)); - } } }, ); @@ -700,26 +694,24 @@ class _InterActionsRowOfTweetState res.fold( (l) { isSavedByMeLocal = true; - if (mounted) - showSmallPopUpMessage( - context: context, - message: l.message, - borderColor: Colors.red, - icon: Icon(Icons.error, color: Colors.red), - ); + showSmallPopUpMessage( + context: context, + message: l.message, + borderColor: Colors.red, + icon: Icon(Icons.error, color: Colors.red), + ); if (mounted) setState(() {}); }, (r) { - if (mounted) - showSmallPopUpMessage( - context: context, - message: "Post removed from your Bookmarks", - borderColor: Colors.blue, - icon: Icon( - Icons.bookmark_remove, - color: Colors.blue, - ), - ); + showSmallPopUpMessage( + context: context, + message: "Post removed from your Bookmarks", + borderColor: Colors.blue, + icon: Icon( + Icons.bookmark_remove, + color: Colors.blue, + ), + ); }, ); }); @@ -729,26 +721,21 @@ class _InterActionsRowOfTweetState res.fold( (l) { isSavedByMeLocal = false; - if (mounted) - showSmallPopUpMessage( - context: context, - message: l.message, - borderColor: Colors.red, - icon: Icon(Icons.error, color: Colors.red), - ); + showSmallPopUpMessage( + context: context, + message: l.message, + borderColor: Colors.red, + icon: Icon(Icons.error, color: Colors.red), + ); if (mounted) setState(() {}); }, (r) { - if (mounted) - showSmallPopUpMessage( - context: context, - message: "Post added to your Bookmarks", - borderColor: Colors.blue, - icon: Icon( - Icons.bookmark_add, - color: Colors.blue, - ), - ); + showSmallPopUpMessage( + context: context, + message: "Post added to your Bookmarks", + borderColor: Colors.blue, + icon: Icon(Icons.bookmark_add, color: Colors.blue), + ); }, ); }); @@ -882,7 +869,6 @@ class _BuildProfileBannerState extends ConsumerState { return res.isNotEmpty ? res : ""; }, error: (err, _) { - ref.refresh(mediaUrlProvider(widget.bannerId)); return ""; }, loading: () { @@ -924,7 +910,6 @@ class _BuildProfileImageState extends ConsumerState { return res.isNotEmpty ? res : unkownUserAvatar; }, error: (err, _) { - ref.read(mediaUrlProvider(widget.avatarId)); return unkownUserAvatar; }, loading: () { @@ -938,15 +923,12 @@ class _BuildProfileImageState extends ConsumerState { } } -List convertJsonListToTweetList( - List jsonList, - bool getReplies, -) { +List convertJsonListToTweetList(List jsonList) { List tweets = []; for (int i = 0; i < jsonList.length; i++) { print(jsonList[i]); final Map json = jsonList[i] as Map; - if (json["tweetType"]?.toLowerCase() == "reply" && !getReplies) continue; + if (json["tweetType"]?.toLowerCase() == "reply") continue; final String profilePhotoId = json["user"]?["profileMedia"]?["id"] ?? ""; @@ -967,66 +949,3 @@ List convertJsonListToTweetList( } return tweets; } - -abstract class TrendsCategoriesTabs { - static String Global = "global"; - static String News = "news"; - static String Sports = "Sports"; - static String Entertainment = "entertainment"; -} - -TweetModel fromProfileTweetModel(ProfileTweetModel profileTweet) { - // Helper to extract media URLs from mediaIds - List _extractMediaUrlsFromIds(List mediaIds) { - // You need to implement this based on your media storage - return mediaIds.map((id) => id).toList(); - } - - // Parse timeAgo to DateTime (simplified - you might need a proper parser) - DateTime _parseTimeAgo(String timeAgo) { - // This is a simplified parser - implement based on your timeAgo format - if (timeAgo.contains('h')) { - int hours = int.tryParse(timeAgo.replaceAll(RegExp(r'[^0-9]'), '')) ?? 0; - return DateTime.now().subtract(Duration(hours: hours)); - } - // Add more cases for days, minutes, etc. - return DateTime.now(); - } - - String _normalizeTweetTypeFromProfile(ProfileTweetModel pt) { - if (pt.type == TweetType.Tweet) return "TWEET"; - if (pt.type == TweetType.Quote) return "QUOTE"; - if (pt.type == TweetType.Reply) - return "REPLY"; - else - return "TWEET"; - } - - return TweetModel( - id: profileTweet.id, - content: profileTweet.text, - authorName: profileTweet.userDisplayName, - authorUsername: profileTweet.userUserName, - authorAvatar: profileTweet.profileMediaId, // Or convert to URL - createdAt: _parseTimeAgo(profileTweet.timeAgo), - likes: profileTweet.likes, - retweets: profileTweet.retweets, - replies: profileTweet.replies, - images: _extractMediaUrlsFromIds(profileTweet.mediaIds), - isLiked: profileTweet.isLikedByMe, - isRetweeted: profileTweet.isRepostedWithMe, - replyToId: profileTweet.type == TweetType.Reply - ? profileTweet.parentId - : null, - replyIds: [], // Not available in ProfileTweetModel - isBookmarked: profileTweet.isSavedByMe, - quotedTweetId: profileTweet.type == TweetType.Quote - ? profileTweet.parentId - : null, - quotedTweet: null, // Not available - quotes: profileTweet.quotesCount.toInt(), - bookmarks: 0, // Not available - userId: profileTweet.userId, - tweetType: _normalizeTweetTypeFromProfile(profileTweet), - ); -} diff --git a/lib/features/profile/repositories/profile_repo_impl.dart b/lib/features/profile/repositories/profile_repo_impl.dart index 79c5752..18ab308 100644 --- a/lib/features/profile/repositories/profile_repo_impl.dart +++ b/lib/features/profile/repositories/profile_repo_impl.dart @@ -1,7 +1,10 @@ import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lite_x/core/providers/current_user_provider.dart'; +import 'package:lite_x/features/media/download_media.dart'; import 'package:lite_x/features/profile/models/create_reply_model.dart'; import 'package:lite_x/features/profile/models/create_tweet_model.dart'; +import 'package:lite_x/features/profile/models/follower_model.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; import 'package:lite_x/features/profile/models/search_user_model.dart'; @@ -13,7 +16,6 @@ import 'package:lite_x/features/profile/repositories/profile_repo.dart'; import 'package:lite_x/features/profile/repositories/profile_storage_service.dart'; import 'package:lite_x/features/profile/view_model/providers.dart'; import 'package:lite_x/features/trends/models/for_you_response_model.dart'; -import 'package:lite_x/features/trends/models/paginated_tweets.dart'; import 'package:lite_x/features/trends/models/trend_category.dart'; import 'package:lite_x/features/trends/models/trend_model.dart'; @@ -44,7 +46,7 @@ class ProfileRepoImpl implements ProfileRepo { return Right(profileData); } on DioException catch (e) { - print("can't get profile data"); + print(e.toString()); if (userName == currentUsername) { await storageService.init(); final localData = await storageService.getProfileData(currentUsername); @@ -105,6 +107,7 @@ class ProfileRepoImpl implements ProfileRepo { return Right(ProfileModel.fromJson(res.data)); } catch (e) { + print("----------\n${e.toString()}\n---------"); return Left(Failure("can't update profile data")); } } @@ -118,11 +121,12 @@ class ProfileRepoImpl implements ProfileRepo { final List jsonList = res.data["data"] ?? []; - final tweets = convertJsonListToTweetList(jsonList, false); + final tweets = convertJsonListToTweetList(jsonList); return Right(tweets); } catch (e) { - return Left(Failure("can't get profile tweets...")); + // print(e.toString() + "--------------+++++++++++++++++"); + return Left(Failure('Failed to load profile posts')); } } @@ -132,11 +136,38 @@ class ProfileRepoImpl implements ProfileRepo { try { // print("Start api ---------------------**"); final res = await _dio.get("api/tweets/$tweetId"); + // print("end api ---------------------**"); final Map json = res.data as Map; - final tweets = convertJsonListToTweetList([json], true); - return Right(tweets[0]); + // get profile photo url and tweet medial urls + final String profilePhotoId = json["user"]?["profileMedia"]?["id"] ?? ""; + + final List tweetMediaIdsDynamic = json["tweetMedia"] ?? []; + final List tweetMediaIds = tweetMediaIdsDynamic + .map((media) => media["media"]?["id"] as String) + .toList(); + + // final List userPhotoUrl = await getMediaUrls([profilePhotoId]); + + // final String profilePhotoUrl = userPhotoUrl[0]; + + // get timeAgo + final String createTime = json["createdAt"] ?? ""; + final String timeAgo = getTimeAgo(createTime); + + json["profileMediaId"] = profilePhotoId; + json["mediaIds"] = tweetMediaIds; + json["timeAgo"] = timeAgo; + + // print( + // "id$i" + + // "${json['profileMediaId']}--------------------*****--------------", + // ); + + // print("end repo posts ----------------**"); + return Right(ProfileTweetModel.fromJson(json)); } catch (e) { + print(e.toString() + "--------------+++++++++++++++++"); return Left(Failure('Failed to load Tweet')); } } @@ -146,28 +177,75 @@ class ProfileRepoImpl implements ProfileRepo { String username, ) async { try { + // print("Start api ---------------------**"); final res = await _dio.get("api/tweets/users/$username"); + // print("end api ---------------------**"); final List jsonList = res.data["data"] ?? []; - final tweets = convertJsonListToTweetList(jsonList, false); + final tweets = convertJsonListToTweetList(jsonList); + // print("end repo posts ----------------**"); return Right(tweets); } catch (e) { + // print(e.toString()); return Left(Failure('Failed to load profile posts')); } } + + + + @override Future>> getProfileLikes( String username, ) async { try { - final res = await _dio.get("api/tweets/likedtweets"); - final List jsonList = res.data["data"] ?? []; - - final tweets = convertJsonListToTweetList(jsonList, false); - return Right(tweets); + try { + // print("Start api ---------------------**"); + final res = await _dio.get("api/tweets/users/$username"); + // print("end api ---------------------**"); + final List jsonList = res.data["data"] ?? []; + + List tweets = []; + for (int i = 0; i < jsonList.length; i++) { + final Map json = jsonList[i] as Map; + if (json["tweetType"]?.toLowerCase() == "reply") continue; + // get profile photo url and tweet medial urls + final String profilePhotoId = + json["user"]?["profileMedia"]?["id"] ?? ""; + + final List tweetMediaIdsDynamic = json["tweetMedia"] ?? []; + final List tweetMediaIds = tweetMediaIdsDynamic + .map((media) => media["mediaId"] as String) + .toList(); + + // final List userPhotoUrl = await getMediaUrls([profilePhotoId]); + + // final String profilePhotoUrl = userPhotoUrl[0]; + + // get timeAgo + final String createTime = json["createdAt"] ?? ""; + final String timeAgo = getTimeAgo(createTime); + + json["profileMediaId"] = profilePhotoId; + json["mediaIds"] = tweetMediaIds; + json["timeAgo"] = timeAgo; + + tweets.add(ProfileTweetModel.fromJson(json)); + // print( + // "id$i" + + // "${json['profileMediaId']}--------------------*****--------------", + // ); + } + // print("end repo posts ----------------**"); + return Right(tweets); + } catch (e) { + // print(e.toString()); + return Left(Failure('Failed to load profile posts')); + } } catch (e) { - return Left(Failure('Failed to load profile posts')); + // print(e.toString()); + return Left(Failure('Failed to load profile likes')); } } @@ -195,15 +273,6 @@ class ProfileRepoImpl implements ProfileRepo { } } - Future> removeBanner(String userId) async { - try { - await _dio.delete("api/users/banner/${userId}"); - return const Right(()); - } catch (e) { - return Left(Failure("couldn't delete profile banner")); - } - } - // user interactions Future>> getFollowers(String userName) async { @@ -222,28 +291,6 @@ class ProfileRepoImpl implements ProfileRepo { } } - Future>> getWhoToFollow() async { - try { - final response = await _dio.get("api/followings/suggested"); - - final people = response.data; - - final List peoplemodels = people - .map((p) { - p["photo"] = p["profileMedia"]?["id"]; - p["isFollowing"] = p["isFollowed"]; - print(p.toString()); - return UserModel.fromJson(p as Map); - }) - .toList() - .cast(); - - return Right(peoplemodels); - } catch (e) { - return Left(Failure("couldn't get who To Follow at this time...")); - } - } - Future>> getFollowings( String userName, ) async { @@ -251,11 +298,6 @@ class ProfileRepoImpl implements ProfileRepo { final response = await _dio.get("api/followings/$userName"); List jsonList = response.data['users'] as List; final List usersList = jsonList - .where( - (json) => - json["followStatus"] == null || - json["followStatus"] == "ACCEPTED", - ) .map((json) => UserModel.fromJson(json)) .toList(); return Right(usersList); @@ -304,38 +346,18 @@ class ProfileRepoImpl implements ProfileRepo { } } - Future> followUser(String username, Ref ref) async { + Future> followUser(String username) async { try { await _dio.post("api/followers/$username"); - - final myUsername = ref.read(myUserNameProvider); - ref.refresh(profileDataProvider(myUsername)); - ref.refresh(profileDataProvider(username)); - ref.refresh(followersProvider(username)); - ref.refresh(followingsProvider(myUsername)); - ref.refresh(followersYouKnowProvider(myUsername)); - ref.refresh(followersYouKnowProvider(username)); - ref.refresh(WhoToFollowProvider); - return const Right(()); } catch (e) { return Left(Failure("couldn't follow user")); } } - Future> unFollowUser(String username, Ref ref) async { + Future> unFollowUser(String username) async { try { await _dio.delete("api/followers/$username"); - - final myUsername = ref.read(myUserNameProvider); - ref.refresh(profileDataProvider(myUsername)); - ref.refresh(profileDataProvider(username)); - ref.refresh(followersProvider(username)); - ref.refresh(followingsProvider(myUsername)); - ref.refresh(followersYouKnowProvider(myUsername)); - ref.refresh(followersYouKnowProvider(username)); - ref.refresh(WhoToFollowProvider); - return const Right(()); } catch (e) { return Left(Failure("couldn't unfollow user")); @@ -343,48 +365,36 @@ class ProfileRepoImpl implements ProfileRepo { } // block and mute - Future> blockUser(String username, Ref ref) async { + Future> blockUser(String username) async { try { await _dio.post("api/blocks/$username"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(getBlockedUsersProvider(myUsername)); - return const Right(()); } catch (e) { return Left(Failure("couldn't block user")); } } - Future> unBlockUser(String username, Ref ref) async { + Future> unBlockUser(String username) async { try { await _dio.delete("api/blocks/$username"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(getBlockedUsersProvider(myUsername)); return const Right(()); } catch (e) { return Left(Failure("couldn't unblock user")); } } - Future> muteUser(String username, Ref ref) async { + Future> muteUser(String username) async { try { await _dio.post("api/mutes/$username"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(getMutedUsersProvider(myUsername)); - ref.refresh(profileDataProvider(username)); return const Right(()); } catch (e) { return Left(Failure("couldn't mute user")); } } - Future> unMuteUser(String username, Ref ref) async { + Future> unMuteUser(String username) async { try { await _dio.delete("api/mutes/$username"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(getMutedUsersProvider(myUsername)); - ref.refresh(profileDataProvider(username)); - return const Right(()); } catch (e) { return Left(Failure("couldn't unmute user")); @@ -436,13 +446,9 @@ class ProfileRepoImpl implements ProfileRepo { } } - Future> deleteTweet(String tweetId, Ref ref) async { + Future> deleteTweet(String tweetId) async { try { await _dio.delete("api/tweets/$tweetId"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(profileMediaProvider(myUsername)); - ref.refresh(profilePostsProvider(myUsername)); - ref.refresh(profileLikesProvider(myUsername)); return Right(()); } catch (e) { return Left(Failure("Can't delete tweet")); @@ -465,29 +471,18 @@ class ProfileRepoImpl implements ProfileRepo { } // tweets interactions - Future> likeTweet(String tweetId, Ref ref) async { + Future> likeTweet(String tweetId) async { try { await _dio.post("api/tweets/$tweetId/likes"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(profileMediaProvider(myUsername)); - ref.refresh(profilePostsProvider(myUsername)); - ref.refresh(profileLikesProvider(myUsername)); return const Right(()); } catch (e) { return Left(Failure("Can't like tweet")); } } - Future> retweetProfileTweet( - String tweetId, - Ref ref, - ) async { + Future> retweetProfileTweet(String tweetId) async { try { await _dio.post("api/tweets/$tweetId/retweets"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(profileMediaProvider(myUsername)); - ref.refresh(profilePostsProvider(myUsername)); - ref.refresh(profileLikesProvider(myUsername)); return const Right(()); } catch (e) { return Left(Failure("Can't retweet tweet, try agail...")); @@ -496,27 +491,18 @@ class ProfileRepoImpl implements ProfileRepo { Future> deleteRetweetProfileTweet( String tweetId, - Ref ref, ) async { try { await _dio.delete("api/tweets/$tweetId/retweets"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(profileMediaProvider(myUsername)); - ref.refresh(profilePostsProvider(myUsername)); - ref.refresh(profileLikesProvider(myUsername)); return const Right(()); } catch (e) { return Left(Failure("Can't delelte retweet tweet, try agail...")); } } - Future> unLikeTweet(String tweetId, Ref ref) async { + Future> unLikeTweet(String tweetId) async { try { await _dio.delete("api/tweets/$tweetId/likes"); - final myUsername = ref.read(myUserNameProvider); - ref.refresh(profileMediaProvider(myUsername)); - ref.refresh(profilePostsProvider(myUsername)); - ref.refresh(profileLikesProvider(myUsername)); return const Right(()); } catch (e) { return Left(Failure("Can't unlike tweet")); @@ -577,6 +563,7 @@ class ProfileRepoImpl implements ProfileRepo { }).toList(); return Right(currentResults); } catch (e) { + print(e.toString() + "-----------@@##"); return Left(Failure("Can't get search results")); } } @@ -586,30 +573,6 @@ class ProfileRepoImpl implements ProfileRepo { await _dio.post("api/auth/change-email", data: {"email": newEmail}); // await Future.delayed(Duration(seconds: 3)); return Right(()); - } on DioException catch (e) { - final statusCode = e.response?.statusCode; - if (statusCode != null && statusCode >= 400 && statusCode < 500) { - final responseData = e.response!.data; - String? errorMessage; - if (responseData is Map && responseData.containsKey('error')) { - errorMessage = responseData['error'] as String; - } else if (responseData is String) { - errorMessage = responseData; - } - return Left( - Failure( - errorMessage != null - ? errorMessage - : "couldn't change email, Please try again later", - ), - ); - } else - return Left( - Failure( - e.response?.statusMessage ?? - "couldn't change email, Please try again later", - ), - ); } catch (e) { print(e); return Left(Failure("couldn't change email, Please try again later")); @@ -629,20 +592,9 @@ class ProfileRepoImpl implements ProfileRepo { ); return Right(()); } on DioException catch (e) { - final statusCode = e.response?.statusCode; - String? errorMessage; - if (statusCode != null && statusCode >= 400 && statusCode < 500) { - final responseData = e.response!.data; - if (responseData is Map && responseData.containsKey('message')) { - errorMessage = responseData['message']; - } else if (responseData is String) { - errorMessage = responseData; - } - } - - return Left( - Failure(errorMessage != null ? errorMessage : "can't verify code"), - ); + final String errorMessage = + e.response?.data["error"] ?? "can't verify code"; + return (Left(Failure(errorMessage))); } catch (e) { return (Left(Failure("can't verify code"))); } @@ -753,59 +705,10 @@ class ProfileRepoImpl implements ProfileRepo { return Right(trends); } return Right([]); - } on DioException { - return (Left( - Failure("cannot get trends at this time, try again later..."), - )); - } catch (e) { - return (Left( - Failure("cannot get trends at this time, try again later..."), - )); - } - } - - Future>> getTweetsForHashtag( - String hashtagId, - ) async { - try { - final res = await _dio.get("api/hashtags/${hashtagId}/tweets"); - final List jsonList = res.data["tweets"] ?? []; - final tweets = convertJsonListToTweetList(jsonList, true); - return Right(tweets); - } catch (e) { - return Left(Failure('Failed to load tweets for this hashtag')); - } - } - - Future> getTweetsForExploreCategory( - String categoryName, { - String? cursor, - }) async { - try { - Response res; - final queryParams = {}; - print("cursor: " + cursor.toString() + "999999999999999999"); - if (categoryName != "general") { - queryParams['category'] = categoryName; - } - - if (cursor != null && cursor.isNotEmpty) { - queryParams['cursor'] = cursor; - } - - res = await _dio.get("api/explore", queryParameters: queryParams); - - final List jsonList = res.data["data"] ?? []; - final String? nextCursor = res.data["cursor"] as String?; - - final tweets = convertJsonListToTweetList(jsonList, false); - - print("cursor: " + cursor.toString() + "999999999999999999"); - - return Right(PaginatedTweets(tweets: tweets, nextCursor: nextCursor)); + } on DioException catch (e) { + return (Left(Failure(e.toString()))); } catch (e) { - print("fail-----------------------------------------------____"); - return Left(Failure('Failed to load ${categoryName} tweets')); + return (Left(Failure(e.toString()))); } } } diff --git a/lib/features/profile/view/widgets/edit_profile/edit_profile_header.dart b/lib/features/profile/view/widgets/edit_profile/edit_profile_header.dart index e1e5460..182f981 100644 --- a/lib/features/profile/view/widgets/edit_profile/edit_profile_header.dart +++ b/lib/features/profile/view/widgets/edit_profile/edit_profile_header.dart @@ -9,6 +9,7 @@ import 'package:lite_x/features/media/view_model/providers.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/profile/view/widgets/edit_profile/controller/edit_profile_controller.dart'; +import 'package:provider/provider.dart'; class EditProfileHeader extends ConsumerWidget { final EditProfileController controller; diff --git a/lib/features/profile/view/widgets/profile/profile_header.dart b/lib/features/profile/view/widgets/profile/profile_header.dart index a272e3c..5064648 100644 --- a/lib/features/profile/view/widgets/profile/profile_header.dart +++ b/lib/features/profile/view/widgets/profile/profile_header.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:lite_x/features/media/view_model/providers.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/profile/view/widgets/profile/block_button.dart'; @@ -11,6 +12,7 @@ import 'package:lite_x/features/profile/view_model/providers.dart'; import 'package:top_snackbar_flutter/custom_snack_bar.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:cached_network_image/cached_network_image.dart'; class ProfileHeader extends ConsumerStatefulWidget { const ProfileHeader({ diff --git a/lib/features/profile/view/widgets/profile_search/explore_profile_screen_body.dart b/lib/features/profile/view/widgets/profile_search/explore_profile_screen_body.dart index 4b602ec..20426b8 100644 --- a/lib/features/profile/view/widgets/profile_search/explore_profile_screen_body.dart +++ b/lib/features/profile/view/widgets/profile_search/explore_profile_screen_body.dart @@ -2,11 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lite_x/core/providers/current_user_provider.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; -import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/profile/view_model/providers.dart'; -import 'package:lite_x/features/trends/view/widgets/category_profile_trend_tab.dart'; import 'package:lite_x/features/trends/view/widgets/for_you_profile_tab.dart'; -import 'package:lite_x/features/trends/view/widgets/trending_profile_tab.dart'; class Exploreprofilescreenbody extends ConsumerStatefulWidget { const Exploreprofilescreenbody({super.key}); @@ -48,7 +45,7 @@ class _ExploreprofilescreenbodyState }, (pm) { return DefaultTabController( - length: 6, + length: 5, child: Scaffold( appBar: TabBar( indicatorColor: Color(0xFF1DA1F2), @@ -66,7 +63,6 @@ class _ExploreprofilescreenbodyState tabs: [ Tab(text: 'For You'), Tab(text: 'Trending'), - Tab(text: 'Global'), Tab(text: 'News'), Tab(text: 'Sports'), Tab(text: 'Entertainment'), @@ -76,7 +72,6 @@ class _ExploreprofilescreenbodyState children: [ _BuildForYouTab(pm), _BuildTrendingTab(pm), - _BuildGlobalTab(pm), _BuildNewsTab(pm), _BuildSportsTab(pm), _BuildEntertainmentTab(pm), @@ -107,11 +102,8 @@ class _ExploreprofilescreenbodyState ); }, loading: () { - return Padding( - padding: const EdgeInsets.all(20), - child: SingleChildScrollView( - child: Center(child: CircularProgressIndicator()), - ), + return SingleChildScrollView( + child: Center(child: CircularProgressIndicator()), ); }, ); @@ -126,30 +118,816 @@ Widget _BuildTrendingTab(ProfileModel pm) { return TrendingProfileTab(pm: pm); } -Widget _BuildGlobalTab(ProfileModel pm) { - return CategoryProfileTrendTab( - pm: pm, - categoryName: TrendsCategoriesTabs.Global, - ); -} - Widget _BuildNewsTab(ProfileModel pm) { - return CategoryProfileTrendTab( - pm: pm, - categoryName: TrendsCategoriesTabs.News, - ); + return NewsProfileTab(); } Widget _BuildSportsTab(ProfileModel pm) { - return CategoryProfileTrendTab( - pm: pm, - categoryName: TrendsCategoriesTabs.Sports, - ); + return SportsProfileTab(); } Widget _BuildEntertainmentTab(ProfileModel pm) { - return CategoryProfileTrendTab( - pm: pm, - categoryName: TrendsCategoriesTabs.Entertainment, - ); + return EntertainmentProfileTab(); +} + +class TrendingProfileTab extends StatelessWidget { + const TrendingProfileTab({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + padding: const EdgeInsets.all(0), + children: [ + // Trending Item 1 + _buildTrendingItem( + position: 1, + category: "Trending in Egypt", + topic: "#انزل_شارك_صوتك_امانه", + postsCount: null, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 2 + _buildTrendingItem( + position: 2, + category: "Trending in Egypt", + topic: "#صوتك_مهم", + postsCount: null, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 3 + _buildTrendingItem( + position: 3, + category: "Trending in Egypt", + topic: "#صوتك_لحماه_الوطن", + postsCount: null, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 4 + _buildTrendingItem( + position: 4, + category: "Trending in Egypt", + topic: "#افضح_المرتزقه", + postsCount: "3,587 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 5 + _buildTrendingItem( + position: 5, + category: "Trending in Egypt", + topic: "#في_حضوك_ياريس_انزل_وشارك", + postsCount: null, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 6 + _buildTrendingItem( + position: 6, + category: "Trending in Egypt", + topic: "اليوم الاتنين", + postsCount: "5,905 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 7 + _buildTrendingItem( + position: 7, + category: "Sports · Trending", + topic: "الونسو", + postsCount: "8,966 posts", + trendingWith: "تشابي", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 8 + _buildTrendingItem( + position: 8, + category: "Trending in Egypt", + topic: "رضا عبد العال", + postsCount: null, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 9 + _buildTrendingItem( + position: 9, + category: "Trending in Egypt", + topic: "الاهلي والزمالك", + postsCount: "12.3K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Item 10 + _buildTrendingItem( + position: 10, + category: "Sports · Trending", + topic: "محمد صلاح", + postsCount: "45.2K posts", + trendingWith: "ليفربول", + ), + + const SizedBox(height: 80), + ], + ); + } + + Widget _buildTrendingItem({ + required int position, + required String category, + required String topic, + String? postsCount, + String? trendingWith, + }) { + return Container( + color: Colors.black, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Position number + SizedBox( + width: 30, + child: Text( + "$position · ", + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ), + + // Content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Category + Text( + category, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + const SizedBox(height: 2), + + // Topic + Text( + topic, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + + // Posts count + if (postsCount != null) ...[ + const SizedBox(height: 2), + Text( + postsCount, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + + // Trending with + if (trendingWith != null) ...[ + const SizedBox(height: 4), + Text( + "Trending with $trendingWith", + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + ], + ), + ), + + // More options icon + Icon(Icons.more_horiz, color: Colors.grey[600], size: 20), + ], + ), + ); + } +} + +class NewsProfileTab extends StatelessWidget { + const NewsProfileTab({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + padding: const EdgeInsets.all(0), + children: [ + // News Item 1 + _buildNewsItem( + category: "Trending in Politics", + topic: "Ivanka Trump", + postsCount: "2,421 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 2 + _buildNewsItem( + category: "Trending in Business & finance", + topic: "Substack", + postsCount: "31.1K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 3 + _buildNewsItem( + category: "Trending in Politics", + topic: "Taiwan to China", + postsCount: "11.9K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 4 + _buildNewsItem( + category: "Trending in Technology", + topic: "Grok 4.1 Fast", + postsCount: "2,217 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 5 + _buildNewsItem( + category: "Trending in Business & finance", + topic: "Grayscale", + postsCount: "15.7K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 6 + _buildNewsItem( + category: "Trending in Business & finance", + topic: "Dogecoin ETF", + postsCount: "2,773 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 7 + _buildNewsItem( + category: "Trending in Business & finance", + topic: "DOGE ETF", + postsCount: "2,898 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 8 + _buildNewsItem( + category: "Trending in Politics", + topic: "Senate Republicans", + postsCount: "8,542 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 9 + _buildNewsItem( + category: "Trending in Technology", + topic: "ChatGPT Plus", + postsCount: "4,156 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Item 10 + _buildNewsItem( + category: "Trending in Business & finance", + topic: "Federal Reserve", + postsCount: "18.3K posts", + ), + + const SizedBox(height: 80), + ], + ); + } + + Widget _buildNewsItem({ + required String category, + required String topic, + required String postsCount, + }) { + return Container( + color: Colors.black, + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Category + Text( + category, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + const SizedBox(height: 4), + + // Topic + Text( + topic, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + + // Posts count + Text( + postsCount, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + ), + ), + + // More options icon + Icon(Icons.more_horiz, color: Colors.grey[600], size: 20), + ], + ), + ); + } +} + +class SportsProfileTab extends StatelessWidget { + const SportsProfileTab({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + padding: const EdgeInsets.all(0), + children: [ + // News Card 1 + _buildNewsCard( + title: "Ronaldo's Stunning Bicycle Kick Seals Al Nassr Win at 40", + timeAgo: "21 hours ago", + category: "Sports", + postsCount: "303K posts", + avatars: 3, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 2 + _buildNewsCard( + title: "Ronaldo's Stunning Bicycle Kick Powers Al Nassr to 4-1 Win", + timeAgo: "13 hours ago", + category: "Sports", + postsCount: "66K posts", + avatars: 3, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 3 + _buildNewsCard( + title: + "Messi's Masterclass Sends Inter Miami to Eastern Conference Final", + timeAgo: "17 hours ago", + category: "Sports", + postsCount: "97K posts", + avatars: 3, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 4 + _buildNewsCard( + title: "Eze's Historic Hat-Trick Powers Arsenal to 4-1 Derby Rout", + timeAgo: "Trending now", + category: "Sports", + postsCount: "4.7K posts", + avatars: 3, + isTrending: true, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 5 + _buildNewsCard( + title: "Real Madrid Draw at Elche Fuels Dressing Room Tension Rumors", + timeAgo: "4 hours ago", + category: "Other", + postsCount: "5.5K posts", + avatars: 3, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Section 1 + _buildTrendingSection( + title: "Trending in Sports", + topic: "#IndianCricket", + postsCount: "5,789 posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Section 2 + _buildTrendingSection( + title: "Trending in Sports", + topic: "#Champions League", + postsCount: "12.4K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Section 3 + _buildTrendingSection( + title: "Trending in Sports", + topic: "Lakers", + postsCount: "8.2K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Section 4 + _buildTrendingSection( + title: "Trending in Sports", + topic: "NBA Playoffs", + postsCount: "15.6K posts", + ), + + const SizedBox(height: 80), + ], + ); + } + + Widget _buildNewsCard({ + required String title, + required String timeAgo, + required String category, + required String postsCount, + required int avatars, + bool isTrending = false, + }) { + return Container( + color: Colors.black, + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + height: 1.3, + ), + ), + const SizedBox(height: 12), + + // Bottom row with avatars and info + Row( + children: [ + // Avatar Stack + SizedBox( + width: avatars * 20.0 + 10, + height: 24, + child: Stack( + children: List.generate( + avatars, + (index) => Positioned( + left: index * 20.0, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _getAvatarColor(index), + border: Border.all(color: Colors.black, width: 1.5), + ), + child: Center( + child: Icon( + Icons.person, + size: 14, + color: Colors.grey[300], + ), + ), + ), + ), + ), + ), + ), + const SizedBox(width: 12), + + // Time and category info + Expanded( + child: Text( + "$timeAgo · $category · $postsCount", + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildTrendingSection({ + required String title, + required String topic, + required String postsCount, + }) { + return Container( + color: Colors.black, + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + const SizedBox(height: 4), + Text( + topic, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Text( + postsCount, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + ), + ), + Icon(Icons.more_horiz, color: Colors.grey[600], size: 20), + ], + ), + ); + } + + Color _getAvatarColor(int index) { + final colors = [ + Colors.blue[800]!, + Colors.red[800]!, + Colors.yellow[700]!, + Colors.green[800]!, + ]; + return colors[index % colors.length]; + } +} + +class EntertainmentProfileTab extends StatelessWidget { + const EntertainmentProfileTab({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + padding: const EdgeInsets.all(0), + children: [ + // News Card 1 + _buildNewsCard( + title: "New Season of Stranger Things Breaks Netflix Records", + timeAgo: "5 hours ago", + category: "Entertainment", + postsCount: "156K posts", + avatars: 3, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 2 + _buildNewsCard( + title: "Taylor Swift Announces Surprise Album Drop at Midnight", + timeAgo: "2 hours ago", + category: "Music", + postsCount: "412K posts", + avatars: 3, + isTrending: true, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 3 + _buildNewsCard( + title: "Marvel Studios Reveals Phase 6 Movie Lineup", + timeAgo: "8 hours ago", + category: "Movies", + postsCount: "89K posts", + avatars: 3, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 4 + _buildNewsCard( + title: "Grammy Awards 2025: Complete Winners List", + timeAgo: "Trending now", + category: "Music", + postsCount: "287K posts", + avatars: 3, + isTrending: true, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // News Card 5 + _buildNewsCard( + title: "HBO's New Drama Series Gets Renewed for Season 2", + timeAgo: "12 hours ago", + category: "TV Shows", + postsCount: "34K posts", + avatars: 3, + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Section 1 + _buildTrendingSection( + title: "Trending in Entertainment", + topic: "#Oscars2025", + postsCount: "178K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F336)), + + // Trending Section 2 + _buildTrendingSection( + title: "Trending in Music", + topic: "Beyoncé", + postsCount: "94.3K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Section 3 + _buildTrendingSection( + title: "Trending in Entertainment", + topic: "#TheLastOfUs", + postsCount: "67.8K posts", + ), + + const Divider(height: 1, color: Color(0xFF2F3336)), + + // Trending Section 4 + _buildTrendingSection( + title: "Trending in Movies", + topic: "Dune Part 3", + postsCount: "45.2K posts", + ), + + const SizedBox(height: 80), + ], + ); + } + + Widget _buildNewsCard({ + required String title, + required String timeAgo, + required String category, + required String postsCount, + required int avatars, + bool isTrending = false, + }) { + return Container( + color: Colors.black, + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + height: 1.3, + ), + ), + const SizedBox(height: 12), + + // Bottom row with avatars and info + Row( + children: [ + // Avatar Stack + SizedBox( + width: avatars * 20.0 + 10, + height: 24, + child: Stack( + children: List.generate( + avatars, + (index) => Positioned( + left: index * 20.0, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _getAvatarColor(index), + border: Border.all(color: Colors.black, width: 1.5), + ), + child: Center( + child: Icon( + Icons.person, + size: 14, + color: Colors.grey[300], + ), + ), + ), + ), + ), + ), + ), + const SizedBox(width: 12), + + // Time and category info + Expanded( + child: Text( + "$timeAgo · $category · $postsCount", + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildTrendingSection({ + required String title, + required String topic, + required String postsCount, + }) { + return Container( + color: Colors.black, + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + const SizedBox(height: 4), + Text( + topic, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Text( + postsCount, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + ), + ), + Icon(Icons.more_horiz, color: Colors.grey[600], size: 20), + ], + ), + ); + } + + Color _getAvatarColor(int index) { + final colors = [ + Colors.purple[800]!, + Colors.pink[800]!, + Colors.orange[700]!, + Colors.teal[800]!, + ]; + return colors[index % colors.length]; + } } diff --git a/lib/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart b/lib/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart index 075af71..ef1b1b7 100644 --- a/lib/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart +++ b/lib/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart @@ -1,9 +1,19 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:lite_x/core/providers/current_user_provider.dart'; +import 'package:lite_x/features/media/view_model/providers.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/profile/view/widgets/profile_tweets/shared_tweet_components.dart'; +import 'package:lite_x/features/profile/view_model/providers.dart'; +import 'package:readmore/readmore.dart'; +import 'package:flutter/gestures.dart'; +import 'package:video_player/video_player.dart'; +import 'package:visibility_detector/visibility_detector.dart'; class ProfileNormalTweetWidget extends ConsumerWidget implements ProfileTweet { const ProfileNormalTweetWidget({ diff --git a/lib/features/profile/view/widgets/profile_tweets/profile_normar_tweet_quot.dart b/lib/features/profile/view/widgets/profile_tweets/profile_normar_tweet_quot.dart index 0fde829..b6ac860 100644 --- a/lib/features/profile/view/widgets/profile_tweets/profile_normar_tweet_quot.dart +++ b/lib/features/profile/view/widgets/profile_tweets/profile_normar_tweet_quot.dart @@ -1,9 +1,15 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:lite_x/core/providers/current_user_provider.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/profile/view/widgets/profile_tweets/shared_tweet_components.dart'; +import 'package:lite_x/features/profile/view_model/providers.dart'; +import 'package:readmore/readmore.dart'; class ProfileNormarTweetQuot extends ConsumerWidget implements ProfileTweet { const ProfileNormarTweetQuot({ diff --git a/lib/features/profile/view/widgets/profile_tweets/profile_retweet_widget.dart b/lib/features/profile/view/widgets/profile_tweets/profile_retweet_widget.dart index f4072ca..7cb336e 100644 --- a/lib/features/profile/view/widgets/profile_tweets/profile_retweet_widget.dart +++ b/lib/features/profile/view/widgets/profile_tweets/profile_retweet_widget.dart @@ -1,13 +1,12 @@ + + import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lite_x/core/providers/current_user_provider.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart'; -import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_quote_widget.dart'; -class ProfileRetweetWidget extends ConsumerWidget implements ProfileTweet { +class ProfileRetweetWidget extends StatelessWidget implements ProfileTweet { const ProfileRetweetWidget({ super.key, required this.tweetModel, @@ -17,11 +16,7 @@ class ProfileRetweetWidget extends ConsumerWidget implements ProfileTweet { final ProfileTweetModel tweetModel; @override - Widget build(BuildContext context, WidgetRef ref) { - final currUser = ref.read(currentUserProvider); - bool myRetweet = false; - if (currUser != null && currUser.username == tweetModel.retweeterUserName) - myRetweet = true; + Widget build(BuildContext context) { return Column( children: [ Padding( @@ -39,7 +34,7 @@ class ProfileRetweetWidget extends ConsumerWidget implements ProfileTweet { ), SizedBox(width: 5), Text( - "${myRetweet ? "You" : this.tweetModel.retweeterName} reposted", + "You reposted", style: TextStyle( color: Colors.grey, fontSize: 14, @@ -49,15 +44,10 @@ class ProfileRetweetWidget extends ConsumerWidget implements ProfileTweet { ], ), ), - tweetModel.type == TweetType.Quote - ? ProfileQuoteWidget( - tweetModel: tweetModel, - profileModel: this.profileModel, - ) - : ProfileNormalTweetWidget( - profileModel: this.profileModel, - profilePostModel: this.tweetModel, - ), + ProfileNormalTweetWidget( + profileModel: this.profileModel, + profilePostModel: this.tweetModel, + ), ], ); } diff --git a/lib/features/profile/view/widgets/profile_tweets/shared_tweet_components.dart b/lib/features/profile/view/widgets/profile_tweets/shared_tweet_components.dart index 00dd7d4..8e1badf 100644 --- a/lib/features/profile/view/widgets/profile_tweets/shared_tweet_components.dart +++ b/lib/features/profile/view/widgets/profile_tweets/shared_tweet_components.dart @@ -62,41 +62,27 @@ class BasicTweetWidget extends ConsumerWidget implements ProfileTweet { child: Row( mainAxisSize: MainAxisSize.min, children: [ - GestureDetector( - onTap: () { - context.push( - "/profilescreen/${this.profilePostModel.userUserName}", - ); - }, - child: Container( - constraints: BoxConstraints(maxWidth: 120), - child: Text( - this.profilePostModel.userDisplayName, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - overflow: TextOverflow.ellipsis, - ), + Container( + constraints: BoxConstraints(maxWidth: 120), + child: Text( + this.profilePostModel.userDisplayName, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + overflow: TextOverflow.ellipsis, ), ), ), const SizedBox(width: 4), Flexible( - child: GestureDetector( - onTap: () { - context.push( - "/profilescreen/${this.profilePostModel.userUserName}", - ); - }, - child: Text( - "@${this.profilePostModel.userUserName}", - style: const TextStyle( - color: Colors.grey, - fontSize: 14, - ), - overflow: TextOverflow.ellipsis, - softWrap: false, + child: Text( + "@${this.profilePostModel.userUserName}", + style: const TextStyle( + color: Colors.grey, + fontSize: 14, ), + overflow: TextOverflow.ellipsis, + softWrap: false, ), ), const SizedBox(width: 5), @@ -157,7 +143,6 @@ class BasicTweetWidget extends ConsumerWidget implements ProfileTweet { padding: const EdgeInsets.only(bottom: 8), child: ExpandableLinkedText( text: profilePostModel.text, - tweet: this.profilePostModel, ), ), if (profilePostModel.mediaIds.isNotEmpty) @@ -256,7 +241,9 @@ class _VideoPlayerWidgetState extends State { @override void dispose() { try { - _controller.dispose(); + if (_controller != null) { + _controller.dispose(); + } } catch (e) { debugPrint('Error disposing video controller: $e'); } @@ -289,7 +276,7 @@ class _VideoPlayerWidgetState extends State { return Container( color: Colors.grey[900], height: widget.height, - child: Center(child: CircularProgressIndicator()), + child: Center(child: CircularProgressIndicator(color: Colors.white)), ); } @@ -424,7 +411,7 @@ class _TweetMediaGridState extends ConsumerState { ); } - Widget _errorContainer(double height, String mediaId, WidgetRef ref) { + Widget _errorContainer(double height) { return Container( height: height, color: Colors.grey[800], @@ -435,12 +422,6 @@ class _TweetMediaGridState extends ConsumerState { Icon(Icons.broken_image, color: Colors.grey, size: 32), SizedBox(height: 8), Text('Couldn\'t load image', style: TextStyle(color: Colors.grey)), - InkWell( - onTap: () { - ref.refresh(mediaUrlProvider(mediaId)); - }, - child: Icon(Icons.refresh, color: Colors.grey), - ), ], ), ), @@ -484,8 +465,8 @@ class _TweetMediaGridState extends ConsumerState { return mediaUrl.when( data: (url) { - if (url.isEmpty) { - return _errorContainer(height, mediaId, ref); + if (url == null || url.isEmpty) { + return _errorContainer(height); } if (_isVideo(url)) { return VideoPlayerWidget( @@ -502,14 +483,14 @@ class _TweetMediaGridState extends ConsumerState { placeholder: (context, url) => _loadingContainer(height), errorWidget: (context, url, error) { debugPrint('Image load error for $mediaId: $error'); - return _errorContainer(height, mediaId, ref); + return _errorContainer(height); }, ); }, loading: () => _loadingContainer(height), error: (error, stack) { debugPrint('Media URL fetch error for $mediaId: $error'); - return _errorContainer(height, mediaId, ref); + return _errorContainer(height); }, ); } @@ -664,12 +645,10 @@ class ExpandableLinkedText extends StatefulWidget { super.key, required this.text, this.trimLines = 3, - required this.tweet, }); final String text; final int trimLines; - final ProfileTweetModel tweet; @override State createState() => _ExpandableLinkedTextState(); @@ -690,7 +669,7 @@ class _ExpandableLinkedTextState extends State { super.dispose(); } - TextSpan _buildSpans(String displayText, List> hashs) { + TextSpan _buildSpans(String displayText) { final regex = RegExp(r'(@[A-Za-z0-9_]+|#[A-Za-z0-9_]+)'); final matches = regex.allMatches(displayText); @@ -719,18 +698,7 @@ class _ExpandableLinkedTextState extends State { context.push("/profilescreen/${token.substring(1)}"); } catch (e) {} } else if (token.contains("#")) { - final hash_word = token.substring(1); - final hash_map = hashs.firstWhere( - (h) => h["hashtagName"] == hash_word.toLowerCase(), - ); - if (hash_map["id"] != null && - hash_map["hashtagName"] != null && - hash_map["id"]!.isNotEmpty && - hash_map["hashtagName"]!.isNotEmpty) - context.push( - "/hashtagTweetsScreen", - extra: [hash_map["id"], hash_map["hashtagName"]], - ); + // TODO: goto hashtag screen } }, ), @@ -799,13 +767,11 @@ class _ExpandableLinkedTextState extends State { } if (!_isTrimmed) { - return RichText( - text: _buildSpans(widget.text, widget.tweet.hashtags), - ); + return RichText(text: _buildSpans(widget.text)); } if (_expanded) { - final full = _buildSpans(widget.text, widget.tweet.hashtags); + final full = _buildSpans(widget.text); final spans = [ full, TextSpan(text: ' ', style: baseStyle), @@ -823,7 +789,7 @@ class _ExpandableLinkedTextState extends State { return RichText(text: TextSpan(children: spans)); } else { final display = _trimmed ?? widget.text; - final mainSpan = _buildSpans(display, widget.tweet.hashtags); + final mainSpan = _buildSpans(display); final spans = [ mainSpan, TextSpan(text: '... ', style: baseStyle), diff --git a/lib/features/profile/view_model/providers.dart b/lib/features/profile/view_model/providers.dart index 22cd78c..63c3856 100644 --- a/lib/features/profile/view_model/providers.dart +++ b/lib/features/profile/view_model/providers.dart @@ -1,4 +1,5 @@ import 'package:dartz/dartz.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lite_x/core/providers/current_user_provider.dart'; import 'package:lite_x/core/providers/dio_interceptor.dart'; diff --git a/lib/features/trends/models/for_you_response_model.dart b/lib/features/trends/models/for_you_response_model.dart index c9bd903..a867bcf 100644 --- a/lib/features/trends/models/for_you_response_model.dart +++ b/lib/features/trends/models/for_you_response_model.dart @@ -1,3 +1,4 @@ +import 'package:lite_x/features/profile/models/follower_model.dart'; import 'package:lite_x/features/profile/models/user_model.dart'; import 'package:lite_x/features/trends/models/trend_category.dart'; diff --git a/lib/features/trends/models/trend_category.dart b/lib/features/trends/models/trend_category.dart index bf7fe1d..db3220a 100644 --- a/lib/features/trends/models/trend_category.dart +++ b/lib/features/trends/models/trend_category.dart @@ -1,6 +1,7 @@ +import 'package:lite_x/features/explore/models/trend_model.dart'; +import 'package:lite_x/features/profile/models/follower_model.dart'; import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; import 'package:lite_x/features/profile/models/shared.dart'; -import 'package:lite_x/features/trends/models/trend_model.dart'; class TrendCategory { final String categoryName; @@ -14,11 +15,8 @@ class TrendCategory { }); factory TrendCategory.fromJson(Map json) { - final viralTweets = convertJsonListToTweetList( - json["viralTweets"] ?? [], - true, - ); - + final viralTweets = convertJsonListToTweetList(json["viralTweets"] ?? []); + final trendsJson = json["trends"] ?? []; final trends = trendsJson .map((t) => TrendModel.fromJson(t)) diff --git a/lib/features/trends/view/widgets/category_profile_trend_tab.dart b/lib/features/trends/view/widgets/category_profile_trend_tab.dart deleted file mode 100644 index b5d6cce..0000000 --- a/lib/features/trends/view/widgets/category_profile_trend_tab.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:lite_x/features/profile/models/profile_model.dart'; -import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; -import 'package:lite_x/features/profile/models/shared.dart'; -import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart' - hide Padding; -import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_quote_widget.dart'; -import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_retweet_widget.dart'; -import 'package:lite_x/features/profile/view_model/providers.dart'; -import 'package:lite_x/features/trends/models/trend_category.dart'; -import 'package:lite_x/features/trends/view/widgets/trend_tile.dart'; - -class CategoryProfileTrendTab extends ConsumerWidget { - const CategoryProfileTrendTab({ - Key? key, - required this.pm, - required this.categoryName, - }) : super(key: key); - final ProfileModel pm; - final String categoryName; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final asyncData = ref.watch(trendCategoryProvider(this.categoryName)); - return asyncData.when( - data: (res) { - return res.fold( - (l) { - return ListView( - padding: EdgeInsets.only(top: 50), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - children: [ - Center(child: Text(l.message)), - Center( - child: IconButton( - onPressed: () async { - // ignore: unused_result - ref.refresh(trendCategoryProvider(this.categoryName)); - }, - icon: Icon(Icons.refresh), - ), - ), - ], - ); - }, - (data) { - return RefreshIndicator( - onRefresh: () async { - // ignore: unused_result - await ref.refresh(trendCategoryProvider(this.categoryName)); - }, - child: ListView( - children: data.trends.isEmpty && data.viralTweets.isEmpty - ? [ - Padding( - padding: const EdgeInsets.all(24), - child: Text( - "Nothing to see here -- yet.", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 35, - ), - ), - ), - ] - : [ - if (data.viralTweets.length > 0) SizedBox(height: 20), - if (data.viralTweets.length > 0) - _buildTweetsSection(data.viralTweets, pm), - if (data.viralTweets.length > 0) - Container( - width: double.infinity, - height: 0.5, - color: Colors.grey, - ), - if (data.viralTweets.length > 0) SizedBox(height: 20), - _buildTredsSection(data, 30), - ], - ), - ); - }, - ); - }, - error: (err, _) { - return ListView( - padding: EdgeInsets.only(top: 50), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - children: [ - Center( - child: Text("Can't get Trends at this time, Try again Later..."), - ), - Center( - child: IconButton( - onPressed: () async { - // ignore: unused_result - ref.refresh(trendCategoryProvider(this.categoryName)); - }, - icon: Icon(Icons.refresh), - ), - ), - ], - ); - }, - loading: () { - return SingleChildScrollView( - padding: EdgeInsets.all(20), - child: Center(child: CircularProgressIndicator()), - ); - }, - ); - } - - Widget _buildTredsSection(TrendCategory category, int limit) { - return ListView.builder( - padding: EdgeInsets.only(left: 16), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (context, index) { - return GestureDetector( - onTap: () { - context.push( - "/hashtagTweetsScreen", - extra: [category.trends[index].id, category.trends[index].title], - ); - }, - child: TrendTile( - trend: category.trends[index], - trendCategory: category.categoryName.length >= 2 - ? "${category.categoryName[0].toUpperCase()}${category.categoryName.substring(1)}" - : "", - showRank: false, - ), - ); - }, - itemCount: category.trends.length <= limit - ? category.trends.length - : limit, - ); - } - - Widget _buildTweetsSection(List tweets, ProfileModel pm) { - if (tweets.isEmpty) { - return SizedBox.shrink(); - } - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - TweetType type = tweets[index].type; - if (type == TweetType.ReTweet) - return ProfileRetweetWidget( - profileModel: pm, - tweetModel: tweets[index], - ); - - if (type == TweetType.Quote) - return ProfileQuoteWidget( - tweetModel: tweets[index], - profileModel: pm, - ); - - return ProfileNormalTweetWidget( - profileModel: pm, - profilePostModel: tweets[index], - ); - }, - itemCount: tweets.length <= 5 ? tweets.length : 5, - separatorBuilder: (context, index) { - return Container( - width: double.infinity, - height: 0.5, - color: Colors.grey, - ); - }, - ), - ], - ); - } -} diff --git a/lib/features/trends/view/widgets/for_you_profile_tab.dart b/lib/features/trends/view/widgets/for_you_profile_tab.dart index 1c0a0ec..26d69b3 100644 --- a/lib/features/trends/view/widgets/for_you_profile_tab.dart +++ b/lib/features/trends/view/widgets/for_you_profile_tab.dart @@ -1,13 +1,13 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import 'package:lite_x/features/profile/models/profile_model.dart'; import 'package:lite_x/features/profile/models/profile_tweet_model.dart'; import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/profile/models/user_model.dart'; import 'package:lite_x/features/profile/view/widgets/following_followers/follower_card.dart'; -import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart' - hide Padding; +import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_normal_tweet_widget.dart' hide Padding; +import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_posts_list.dart'; import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_quote_widget.dart'; import 'package:lite_x/features/profile/view/widgets/profile_tweets/profile_retweet_widget.dart'; import 'package:lite_x/features/profile/view_model/providers.dart'; @@ -57,13 +57,13 @@ class ForYouProfileTab extends ConsumerWidget { children: [ asyncTrends.when( data: (res) => res.fold( - (l) => SizedBox.shrink(), + (l) => Text(l.message), (r) => _buildTredsSection(r), ), error: (err, _) => SizedBox.shrink(), loading: () => SizedBox.shrink(), ), - _buildWhoToFollowSection(data.suggestedUsers, context), + _buildWhoToFollowSection(data.suggestedUsers), ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), @@ -100,7 +100,6 @@ class ForYouProfileTab extends ConsumerWidget { }, loading: () { return SingleChildScrollView( - padding: EdgeInsets.all(20), child: Center(child: CircularProgressIndicator()), ); }, @@ -113,25 +112,17 @@ class ForYouProfileTab extends ConsumerWidget { physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { - return GestureDetector( - onTap: () { - context.push( - "/hashtagTweetsScreen", - extra: [trends[index].id, trends[index].title], - ); - }, - child: TrendTile( - trend: trends[index], - trendCategory: "ُEgypt", - showRank: false, - ), + return TrendTile( + trend: trends[index], + trendCategory: "ُEgypt", + showRank: false, ); }, itemCount: trends.length <= 6 ? trends.length : 6, ); } - Widget _buildWhoToFollowSection(List users, BuildContext context) { + Widget _buildWhoToFollowSection(List users) { return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -145,28 +136,12 @@ class ForYouProfileTab extends ConsumerWidget { style: TextStyle(fontSize: 22, fontWeight: FontWeight.w900), ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => - FollowerCard(user: users[index], isMe: true), - itemCount: users.length <= 5 ? users.length : 5, - ), - Padding( - padding: const EdgeInsets.only(left: 16, bottom: 16), - child: GestureDetector( - onTap: () { - // TODO: go to who to follow screen - // context.push(); - context.push("/whoToFollowScreen"); - }, - child: Text("Show more", style: TextStyle(color: Colors.blue)), - ), - ), - ], + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => + FollowerCard(user: users[index], isMe: true), + itemCount: users.length <= 5 ? users.length : 5, ), ], ); @@ -183,9 +158,8 @@ class ForYouProfileTab extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container(width: double.infinity, height: 0.25, color: Colors.grey), Padding( - padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), + padding: const EdgeInsets.only(left: 16, top: 16), child: Text( category.categoryName.length >= 2 ? "${category.categoryName[0].toUpperCase()}${category.categoryName.substring(1)} Trends" diff --git a/lib/features/trends/view/widgets/trend_tile.dart b/lib/features/trends/view/widgets/trend_tile.dart index 9b3b5a5..9792545 100644 --- a/lib/features/trends/view/widgets/trend_tile.dart +++ b/lib/features/trends/view/widgets/trend_tile.dart @@ -19,7 +19,7 @@ class TrendTile extends StatelessWidget { final colorScheme = Theme.of(context).colorScheme; return Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), + padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -39,10 +39,9 @@ class TrendTile extends StatelessWidget { const SizedBox(height: 4), // Title Text( - "#" + trend.title, + trend.title, style: textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 18, + fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), diff --git a/lib/features/trends/view/widgets/trending_profile_tab.dart b/lib/features/trends/view/widgets/trending_profile_tab.dart deleted file mode 100644 index f28402c..0000000 --- a/lib/features/trends/view/widgets/trending_profile_tab.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:lite_x/features/profile/models/profile_model.dart'; -import 'package:lite_x/features/profile/view_model/providers.dart'; -import 'package:lite_x/features/trends/view/widgets/trend_tile.dart'; - -class TrendingProfileTab extends ConsumerWidget { - const TrendingProfileTab({Key? key, required this.pm}) : super(key: key); - final ProfileModel pm; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final asyncTrends = ref.watch(profileTrendsProvider); - return asyncTrends.when( - data: (res) { - return res.fold( - (l) { - return ListView( - padding: EdgeInsets.only(top: 50), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - children: [ - Center(child: Text(l.message)), - Center( - child: IconButton( - onPressed: () async { - // ignore: unused_result - ref.refresh(profileTrendsProvider); - }, - icon: Icon(Icons.refresh), - ), - ), - ], - ); - }, - (data) { - return RefreshIndicator( - onRefresh: () async { - // ignore: unused_result - await ref.refresh(profileTrendsProvider); - }, - child: data.isNotEmpty - ? ListView.builder( - padding: EdgeInsets.only(left: 16), - itemBuilder: (context, index) => InkWell( - onTap: () { - context.push( - "/hashtagTweetsScreen", - extra: [data[index].id, data[index].title], - ); - }, - child: TrendTile( - trend: data[index], - trendCategory: "Egypt", - showRank: true, - ), - ), - itemCount: data.length, - ) - : Padding( - padding: const EdgeInsets.all(24), - child: Text( - "Nothing to see here -- yet.", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 35, - ), - ), - ), - ); - }, - ); - }, - error: (err, _) { - return ListView( - padding: EdgeInsets.only(top: 50), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - children: [ - Center( - child: Text("Can't get Trends at this time, Try again Later..."), - ), - Center( - child: IconButton( - onPressed: () async { - // ignore: unused_result - ref.refresh(profileTrendsProvider); - }, - icon: Icon(Icons.refresh), - ), - ), - ], - ); - }, - loading: () { - return SingleChildScrollView( - padding: EdgeInsets.all(20), - child: Center(child: CircularProgressIndicator()), - ); - }, - ); - } -}