From a4bd26fd4dffa8eeda5f97daa0edb1466612b8c3 Mon Sep 17 00:00:00 2001 From: Beshoy Sorial Date: Sat, 13 Dec 2025 21:40:01 +0200 Subject: [PATCH 01/10] Add test stage back to Dockerfile Reintroduced the test stage in the Dockerfile to run unit and widget tests. --- Dockerfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 448ee04..96fecab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,12 +9,6 @@ RUN flutter pub get # Copy source code COPY . . -# ---------------- TEST STAGE ---------------- -FROM base AS test -WORKDIR /app - -# Run unit/widget tests -RUN flutter test --no-pub # ---------------- LINT STAGE ---------------- FROM ghcr.io/cirruslabs/flutter:3.35.5 AS lint @@ -41,3 +35,10 @@ RUN sdkmanager \ # Now build release APK RUN flutter build apk --release + +# ---------------- TEST STAGE ---------------- +FROM base AS test +WORKDIR /app + +# Run unit/widget tests +RUN flutter test --no-pub From 5c60764f8789ff6cae6be14a6c43f89f1451bee8 Mon Sep 17 00:00:00 2001 From: asermohamed1 <153523890+asermohamed1@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:22:00 +0200 Subject: [PATCH 02/10] modifiy docker file --- .env | 6 +++--- Dockerfile | 14 +++++++------- .../auth/view_model/auth_view_model_test.dart | 1 + 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.env b/.env index 9a56f2d..615c990 100644 --- a/.env +++ b/.env @@ -1,9 +1,9 @@ -API_URL=https://avah-pollinical-randal.ngrok-free.dev/ -# API_test_URL=https://example.com/ +# API_URL=https://avah-pollinical-randal.ngrok-free.dev/ +API_test_URL=https://example.com/ # API_URL=https://wanita-hypernormal-cherise.ngrok-free.dev/ # API_URL=https://avah-pollinical-randal.ngrok-free.dev/ # API_URL=https://0f9eef01f130.ngrok-free.app/ # API_URL=https://ingeborg-untrammed-leo.ngrok-free.dev/ # Socket_Url=https://app-dbef67eb-9a2e-44fa-abff-3e8b83204d9c.cleverapps.io # serverClientId=1096363232606-2fducjadk56bt4nsreqkj2jna7oiomga.apps.googleusercontent.com - +API_URL=https://node.shoy.publicvm.com/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 96fecab..01741a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,9 +9,15 @@ RUN flutter pub get # Copy source code COPY . . +RUN yes | sdkmanager --licenses +RUN sdkmanager \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" \ + "cmdline-tools;latest" # ---------------- LINT STAGE ---------------- -FROM ghcr.io/cirruslabs/flutter:3.35.5 AS lint +FROM base AS lint WORKDIR /app COPY pubspec.* ./ @@ -25,13 +31,7 @@ RUN flutter analyze FROM base AS build-apk # Install Android SDK components early for caching -RUN yes | sdkmanager --licenses -RUN sdkmanager \ - "platform-tools" \ - "platforms;android-34" \ - "build-tools;34.0.0" \ - "cmdline-tools;latest" # Now build release APK RUN flutter build apk --release diff --git a/test/features/auth/view_model/auth_view_model_test.dart b/test/features/auth/view_model/auth_view_model_test.dart index 9b8804e..8b53852 100644 --- a/test/features/auth/view_model/auth_view_model_test.dart +++ b/test/features/auth/view_model/auth_view_model_test.dart @@ -58,6 +58,7 @@ void main() { authLocalRepositoryProvider.overrideWithValue(mockLocalRepository), ], ); + container.read(authViewModelProvider); }); tearDown(() { From 2d78082fb61dd0766a05ec7f2fe64242fa989725 Mon Sep 17 00:00:00 2001 From: asermohamed1 <153523890+asermohamed1@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:52:36 +0200 Subject: [PATCH 03/10] fixed docker file and Palette --- lib/core/theme/{palette.dart => Palette.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/core/theme/{palette.dart => Palette.dart} (100%) diff --git a/lib/core/theme/palette.dart b/lib/core/theme/Palette.dart similarity index 100% rename from lib/core/theme/palette.dart rename to lib/core/theme/Palette.dart From b0d53a238a110863bb302db304c0be993c0856ab Mon Sep 17 00:00:00 2001 From: asermohamed1 <153523890+asermohamed1@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:53:00 +0200 Subject: [PATCH 04/10] fixed docker file --- Dockerfile | 11 ++++++----- Jenkinsfile | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 01741a1..e66d1aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,12 @@ RUN flutter pub get COPY . . RUN flutter analyze +# ---------------- TEST STAGE ---------------- +FROM base AS test +WORKDIR /app + +# Run unit/widget tests +RUN flutter test --no-pub # ---------------- BUILD APK STAGE ---------------- FROM base AS build-apk @@ -36,9 +42,4 @@ FROM base AS build-apk # Now build release APK RUN flutter build apk --release -# ---------------- TEST STAGE ---------------- -FROM base AS test -WORKDIR /app -# Run unit/widget tests -RUN flutter test --no-pub diff --git a/Jenkinsfile b/Jenkinsfile index 40589ea..99d657f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -102,11 +102,11 @@ EOF script { sh ''' echo "Kaniko building test target (no push)..." - #/kaniko/executor \ - # --context=. \ - #--dockerfile=Dockerfile \ - #--no-push \ - #--target=test + /kaniko/executor \ + --context=. \ + --dockerfile=Dockerfile \ + --no-push \ + --target=test ''' } } From 3a120973f7d5bbc3ef5db5015cbfa0ee253138e2 Mon Sep 17 00:00:00 2001 From: Beshoy Sorial Date: Sun, 14 Dec 2025 21:26:08 +0200 Subject: [PATCH 05/10] Update Kaniko build messages and options --- Jenkinsfile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 99d657f..d3c3e7b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -80,11 +80,13 @@ EOF script { try { sh ''' - echo "Kaniko building lint target (no push, no cache)..." + echo "Kaniko building lint target..." /kaniko/executor \ --context=. \ --dockerfile=Dockerfile \ - --no-push \ + --destination=${DOCKER_IMAGE}:build-TEST \ + --cache=true \ + --cache-ttl=24h \ --target=lint ''' } catch (err) { @@ -101,11 +103,13 @@ EOF container('kaniko') { script { sh ''' - echo "Kaniko building test target (no push)..." + echo "Kaniko building test target..." /kaniko/executor \ --context=. \ --dockerfile=Dockerfile \ - --no-push \ + --destination=${DOCKER_IMAGE}:build-TEST \ + --cache=true \ + --cache-ttl=24h \ --target=test ''' } From c048240781bacae3d43fcbf7abbe079c740837fb Mon Sep 17 00:00:00 2001 From: asermohamed1 <153523890+asermohamed1@users.noreply.github.com> Date: Sun, 14 Dec 2025 22:29:21 +0200 Subject: [PATCH 06/10] fixed Palette --- Dockerfile | 7 -- .../view/screens/Notification_Screen.dart | 8 +- .../view/widgets/card/all_tweet_card.dart | 74 ++++++--------- .../view/widgets/card/interaction_bar.dart | 9 +- .../widgets/card/mentions_tweet_card.dart | 16 ++-- .../view/widgets/empty/all_empty.dart | 2 +- .../view/widgets/empty/mention_empty.dart | 4 +- .../view/widgets/empty/verified_empty.dart | 2 +- .../view/widgets/notification_tabs.dart | 11 +-- .../view/widgets/status_bar.dart | 20 ++--- .../view/widgets/tabs/all_notifications.dart | 31 ++++--- .../widgets/tabs/mentions_notifications.dart | 14 +-- .../widgets/tabs/verified_notifications.dart | 2 +- lib/features/search/view/search_screen.dart | 90 ++++++++++--------- .../search/view/widgets/people_card.dart | 15 ++-- .../search/view/widgets/search_bar.dart | 90 +++++++++---------- .../search/view/widgets/tweet_card.dart | 19 ++-- 17 files changed, 188 insertions(+), 226 deletions(-) diff --git a/Dockerfile b/Dockerfile index e66d1aa..6bf84e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,13 +9,6 @@ RUN flutter pub get # Copy source code COPY . . -RUN yes | sdkmanager --licenses - -RUN sdkmanager \ - "platform-tools" \ - "platforms;android-34" \ - "build-tools;34.0.0" \ - "cmdline-tools;latest" # ---------------- LINT STAGE ---------------- FROM base AS lint WORKDIR /app diff --git a/lib/features/notifications/view/screens/Notification_Screen.dart b/lib/features/notifications/view/screens/Notification_Screen.dart index 38f2105..2957e75 100644 --- a/lib/features/notifications/view/screens/Notification_Screen.dart +++ b/lib/features/notifications/view/screens/Notification_Screen.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/features/home/view/widgets/profile_side_drawer.dart'; import '../widgets/notification_tabs.dart'; import '../widgets/status_bar.dart'; @@ -24,12 +24,10 @@ class _NotificationScreenState extends State { child: Column( children: [ Statusbar(scaffoldKey: _scaffoldKey), - const Expanded( - child: NotificationTabs(), - ), + const Expanded(child: NotificationTabs()), ], ), ), ); } -} \ No newline at end of file +} diff --git a/lib/features/notifications/view/widgets/card/all_tweet_card.dart b/lib/features/notifications/view/widgets/card/all_tweet_card.dart index 7581985..a5a58b2 100644 --- a/lib/features/notifications/view/widgets/card/all_tweet_card.dart +++ b/lib/features/notifications/view/widgets/card/all_tweet_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lite_x/core/providers/dio_interceptor.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/features/home/repositories/home_repository.dart'; import 'package:lite_x/features/home/view/screens/quote_composer_screen.dart'; import 'package:lite_x/features/home/view/screens/tweet_screen.dart'; @@ -116,7 +116,9 @@ class _AllTweetCardWidgetState extends ConsumerState { _showSnack('User profile not available'); return; } - Navigator.of(context).pushNamed('/profile', arguments: {'username': username}); + Navigator.of( + context, + ).pushNamed('/profile', arguments: {'username': username}); } Future _toggleLike() async { @@ -164,7 +166,9 @@ class _AllTweetCardWidgetState extends ConsumerState { }); final currentTweetId = tweetId; if (currentTweetId != null) { - ref.read(notificationViewModelProvider.notifier).updateTweetInteractions( + ref + .read(notificationViewModelProvider.notifier) + .updateTweetInteractions( currentTweetId, likesCount: previousCount, isLiked: previousLiked, @@ -222,7 +226,9 @@ class _AllTweetCardWidgetState extends ConsumerState { }); final currentTweetId = tweetId; if (currentTweetId != null) { - ref.read(notificationViewModelProvider.notifier).updateTweetInteractions( + ref + .read(notificationViewModelProvider.notifier) + .updateTweetInteractions( currentTweetId, repostsCount: previousCount, isRetweeted: previousState, @@ -260,19 +266,15 @@ class _AllTweetCardWidgetState extends ConsumerState { } final vm = ref.read(notificationViewModelProvider.notifier); - vm.updateTweetInteractions( - tweetId, - isBookmarked: _bookmarked, - ); + vm.updateTweetInteractions(tweetId, isBookmarked: _bookmarked); } catch (_) { if (mounted) { setState(() { _bookmarked = previousBookmarked; }); - ref.read(notificationViewModelProvider.notifier).updateTweetInteractions( - tweetId, - isBookmarked: previousBookmarked, - ); + ref + .read(notificationViewModelProvider.notifier) + .updateTweetInteractions(tweetId, isBookmarked: previousBookmarked); } _showSnack('Unable to update bookmark right now.'); } finally { @@ -372,9 +374,7 @@ class _AllTweetCardWidgetState extends ConsumerState { margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), padding: padding ?? const EdgeInsets.symmetric(vertical: 12.0), decoration: const BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.white, width: 0.5), - ), + border: Border(bottom: BorderSide(color: Colors.white, width: 0.5)), ), child: child, ); @@ -394,10 +394,7 @@ class _AllTweetCardWidgetState extends ConsumerState { return Container( width: 36, height: 36, - decoration: BoxDecoration( - color: Colors.black, - shape: BoxShape.circle, - ), + decoration: BoxDecoration(color: Colors.black, shape: BoxShape.circle), child: Icon(icon, color: color, size: 30), ); } @@ -495,7 +492,6 @@ class _AllTweetCardWidgetState extends ConsumerState { (notification.quotedContent?.isNotEmpty ?? false); } - Widget _metricButton({ required IconData icon, int? count, @@ -548,10 +544,8 @@ class _AllTweetCardWidgetState extends ConsumerState { onTap: _toggleLike, ), _metricButton( - icon: - _bookmarked ? Icons.bookmark : Icons.bookmark_border, - color: - _bookmarked ? Palette.primary : Palette.textTertiary, + icon: _bookmarked ? Icons.bookmark : Icons.bookmark_border, + color: _bookmarked ? Palette.primary : Palette.textTertiary, onTap: _toggleBookmark, ), _metricButton( @@ -599,12 +593,7 @@ class _AllTweetCardWidgetState extends ConsumerState { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Text( - notification.body, - style: _bodyStyle, - ), - ), + Expanded(child: Text(notification.body, style: _bodyStyle)), const SizedBox(width: 12), _buildTimestampText(), ], @@ -622,8 +611,7 @@ class _AllTweetCardWidgetState extends ConsumerState { // Prefer tweet content as snapshot; fall back to notification body final String snapshotText; - if (notification.tweet != null && - notification.tweet!.content.isNotEmpty) { + if (notification.tweet != null && notification.tweet!.content.isNotEmpty) { snapshotText = notification.tweet!.content; } else { snapshotText = notification.body; @@ -649,10 +637,7 @@ class _AllTweetCardWidgetState extends ConsumerState { text: '${notification.actor.name} ', style: _nameStyle, children: [ - TextSpan( - text: description, - style: _secondaryStyle, - ), + TextSpan(text: description, style: _secondaryStyle), ], ), ), @@ -663,10 +648,7 @@ class _AllTweetCardWidgetState extends ConsumerState { ), if (snapshotText.isNotEmpty) ...[ const SizedBox(height: 6), - Text( - snapshotText, - style: _secondaryStyle, - ), + Text(snapshotText, style: _secondaryStyle), ], ], ), @@ -684,10 +666,7 @@ class _AllTweetCardWidgetState extends ConsumerState { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - BuildSmallProfileImage( - mediaId: notification.mediaUrl, - radius: 20, - ), + BuildSmallProfileImage(mediaId: notification.mediaUrl, radius: 20), const SizedBox(width: 12), Expanded( child: Column( @@ -702,10 +681,7 @@ class _AllTweetCardWidgetState extends ConsumerState { text: '${notification.actor.name} ', style: _nameStyle, children: [ - TextSpan( - text: description, - style: _secondaryStyle, - ), + TextSpan(text: description, style: _secondaryStyle), ], ), ), @@ -819,4 +795,4 @@ class _AllTweetCardWidgetState extends ConsumerState { } return _buildConversationCard(); } -} \ No newline at end of file +} diff --git a/lib/features/notifications/view/widgets/card/interaction_bar.dart b/lib/features/notifications/view/widgets/card/interaction_bar.dart index 9f2b53d..cff0c4b 100644 --- a/lib/features/notifications/view/widgets/card/interaction_bar.dart +++ b/lib/features/notifications/view/widgets/card/interaction_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:dio/dio.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/core/providers/dio_interceptor.dart'; import 'package:lite_x/features/home/repositories/home_repository.dart'; import 'package:lite_x/features/home/view/screens/quote_composer_screen.dart'; @@ -245,12 +245,7 @@ class _InteractionBarState extends ConsumerState { size: 18, ), ), - _buildButton( - Icons.ios_share_outlined, - 0, - Palette.reply, - _handleQuote, - ), + _buildButton(Icons.ios_share_outlined, 0, Palette.reply, _handleQuote), ], ); } diff --git a/lib/features/notifications/view/widgets/card/mentions_tweet_card.dart b/lib/features/notifications/view/widgets/card/mentions_tweet_card.dart index 463ab61..489f8c5 100644 --- a/lib/features/notifications/view/widgets/card/mentions_tweet_card.dart +++ b/lib/features/notifications/view/widgets/card/mentions_tweet_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/core/providers/dio_interceptor.dart'; import 'package:lite_x/features/profile/models/shared.dart'; import 'package:lite_x/features/home/repositories/home_repository.dart'; @@ -58,9 +58,9 @@ class _MentionTweetCardState extends ConsumerState { void _showSnack(String message) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(message)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); } void _openTweetDetail() { @@ -75,7 +75,9 @@ class _MentionTweetCardState extends ConsumerState { _showSnack('User profile not available'); return; } - Navigator.of(context).pushNamed('/profile', arguments: {'username': username}); + Navigator.of( + context, + ).pushNamed('/profile', arguments: {'username': username}); } Future _openQuoteComposer() async { @@ -304,9 +306,7 @@ class _MentionTweetCardState extends ConsumerState { margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), padding: padding ?? const EdgeInsets.symmetric(vertical: 12.0), decoration: const BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.white, width: 0.5), - ), + border: Border(bottom: BorderSide(color: Colors.white, width: 0.5)), ), child: child, ); diff --git a/lib/features/notifications/view/widgets/empty/all_empty.dart b/lib/features/notifications/view/widgets/empty/all_empty.dart index 3cb180b..27905da 100644 --- a/lib/features/notifications/view/widgets/empty/all_empty.dart +++ b/lib/features/notifications/view/widgets/empty/all_empty.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; class AllEmptyStateWidget extends StatelessWidget { const AllEmptyStateWidget({super.key}); diff --git a/lib/features/notifications/view/widgets/empty/mention_empty.dart b/lib/features/notifications/view/widgets/empty/mention_empty.dart index 699fd23..a591e28 100644 --- a/lib/features/notifications/view/widgets/empty/mention_empty.dart +++ b/lib/features/notifications/view/widgets/empty/mention_empty.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; class MentionsEmptyStateWidget extends StatelessWidget { const MentionsEmptyStateWidget({super.key}); @@ -41,4 +41,4 @@ class MentionsEmptyStateWidget extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/notifications/view/widgets/empty/verified_empty.dart b/lib/features/notifications/view/widgets/empty/verified_empty.dart index c9c7a69..8ff8fb9 100644 --- a/lib/features/notifications/view/widgets/empty/verified_empty.dart +++ b/lib/features/notifications/view/widgets/empty/verified_empty.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; class VerifiedEmptyStateWidget extends StatelessWidget { const VerifiedEmptyStateWidget({super.key}); diff --git a/lib/features/notifications/view/widgets/notification_tabs.dart b/lib/features/notifications/view/widgets/notification_tabs.dart index dab9261..e540aa9 100644 --- a/lib/features/notifications/view/widgets/notification_tabs.dart +++ b/lib/features/notifications/view/widgets/notification_tabs.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/features/notifications/notification_fcm_service.dart'; import 'tabs/all_notifications.dart'; import 'tabs/verified_notifications.dart'; @@ -70,12 +70,7 @@ class _NotificationTabsState extends ConsumerState Container( decoration: BoxDecoration( color: Palette.background, - border: Border( - bottom: BorderSide( - color: Palette.border, - width: 1, - ), - ), + border: Border(bottom: BorderSide(color: Palette.border, width: 1)), ), height: 53, child: Row( @@ -141,4 +136,4 @@ class _NotificationTabsState extends ConsumerState ], ); } -} \ No newline at end of file +} diff --git a/lib/features/notifications/view/widgets/status_bar.dart b/lib/features/notifications/view/widgets/status_bar.dart index 97455ce..3bebf14 100644 --- a/lib/features/notifications/view/widgets/status_bar.dart +++ b/lib/features/notifications/view/widgets/status_bar.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:lite_x/core/models/usermodel.dart'; import 'package:lite_x/core/providers/current_user_provider.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/features/profile/models/shared.dart'; class Statusbar extends ConsumerWidget { @@ -28,16 +28,16 @@ class Statusbar extends ConsumerWidget { GestureDetector( onTap: () => scaffoldKey.currentState?.openDrawer(), child: ClipOval( - child: SizedBox( - width: 40, - height: 40, - child: BuildSmallProfileImage( - mediaId: avatarUrl, - radius: 20, + child: SizedBox( + width: 40, + height: 40, + child: BuildSmallProfileImage( + mediaId: avatarUrl, + radius: 20, + ), + ), ), ), - ), - ), SizedBox(width: 12), Text( 'Notifications', @@ -65,4 +65,4 @@ class Statusbar extends ConsumerWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/notifications/view/widgets/tabs/all_notifications.dart b/lib/features/notifications/view/widgets/tabs/all_notifications.dart index 061ca3b..bf40436 100644 --- a/lib/features/notifications/view/widgets/tabs/all_notifications.dart +++ b/lib/features/notifications/view/widgets/tabs/all_notifications.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import '../empty/all_empty.dart'; import '../card/all_tweet_card.dart'; @@ -38,9 +38,7 @@ class _AllTabState extends ConsumerState data: (items) { return RefreshIndicator( onRefresh: () async { - await ref - .read(notificationViewModelProvider.notifier) - .refresh(); + await ref.read(notificationViewModelProvider.notifier).refresh(); }, child: items.isEmpty ? ListView( @@ -48,13 +46,13 @@ class _AllTabState extends ConsumerState children: const [AllEmptyStateWidget()], ) : AnimatedList( - key: _listKey, - padding: const EdgeInsets.symmetric(vertical: 8.0), - initialItemCount: items.length, - itemBuilder: (context, index, animation) { - return _buildItem(items[index], animation); - }, - ), + key: _listKey, + padding: const EdgeInsets.symmetric(vertical: 8.0), + initialItemCount: items.length, + itemBuilder: (context, index, animation) { + return _buildItem(items[index], animation); + }, + ), ); }, loading: () => const Center(child: CircularProgressIndicator()), @@ -80,12 +78,17 @@ class _AllTabState extends ConsumerState ); } - Widget _buildItem(NotificationItem notification, Animation animation) { + Widget _buildItem( + NotificationItem notification, + Animation animation, + ) { return FadeTransition( opacity: animation, child: SlideTransition( - position: Tween(begin: const Offset(0, 0.2), end: Offset.zero) - .animate(animation), + position: Tween( + begin: const Offset(0, 0.2), + end: Offset.zero, + ).animate(animation), child: Padding( padding: const EdgeInsets.only(bottom: 16.0), child: AllTweetCardWidget(notification: notification), diff --git a/lib/features/notifications/view/widgets/tabs/mentions_notifications.dart b/lib/features/notifications/view/widgets/tabs/mentions_notifications.dart index 2729186..b6a48dd 100644 --- a/lib/features/notifications/view/widgets/tabs/mentions_notifications.dart +++ b/lib/features/notifications/view/widgets/tabs/mentions_notifications.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import '../empty/mention_empty.dart'; import '../card/mentions_tweet_card.dart'; import '../../../mentions_view_model.dart'; @@ -42,12 +42,12 @@ class _MentionsTabState extends ConsumerState children: const [MentionsEmptyStateWidget()], ) : ListView.builder( - padding: const EdgeInsets.symmetric(vertical: 8.0), - itemCount: items.length, - itemBuilder: (context, index) { - return MentionTweetCard(mention: items[index]); - }, - ), + padding: const EdgeInsets.symmetric(vertical: 8.0), + itemCount: items.length, + itemBuilder: (context, index) { + return MentionTweetCard(mention: items[index]); + }, + ), ); }, loading: () => const Center(child: CircularProgressIndicator()), diff --git a/lib/features/notifications/view/widgets/tabs/verified_notifications.dart b/lib/features/notifications/view/widgets/tabs/verified_notifications.dart index d512a32..c47fd32 100644 --- a/lib/features/notifications/view/widgets/tabs/verified_notifications.dart +++ b/lib/features/notifications/view/widgets/tabs/verified_notifications.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import '../../../notification_model.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import '../empty/verified_empty.dart'; class VerifiedTab extends StatefulWidget { diff --git a/lib/features/search/view/search_screen.dart b/lib/features/search/view/search_screen.dart index b2ec06e..7f1de33 100644 --- a/lib/features/search/view/search_screen.dart +++ b/lib/features/search/view/search_screen.dart @@ -4,14 +4,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:lite_x/core/routes/Route_Constants.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/features/search/providers/search_providers.dart'; import 'package:lite_x/features/search/view/search_results_screen.dart'; import 'package:lite_x/features/search/view/widgets/search_bar.dart'; import 'package:lite_x/features/search/view/widgets/people_card.dart'; import 'package:lite_x/features/profile/models/shared.dart'; - class SearchScreen extends ConsumerStatefulWidget { final Map? extra; @@ -61,10 +60,7 @@ class _SearchScreenState extends ConsumerState { ref.read(searchHistoryProvider.notifier).add(trimmed); context.pushNamed( RouteConstants.SearchScreen, - extra: { - 'query': trimmed, - 'showResults': true, - }, + extra: {'query': trimmed, 'showResults': true}, ); } @@ -91,19 +87,16 @@ class _SearchScreenState extends ConsumerState { if (!hasQuery) ...[ if (history.isEmpty) Padding( - padding: const EdgeInsets.all(16), - child: SizedBox( - width: double.infinity, - child: Text( - 'Try searching for people, lists, or keywords', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Colors.grey, - ), - ), + padding: const EdgeInsets.all(16), + child: SizedBox( + width: double.infinity, + child: Text( + 'Try searching for people, lists, or keywords', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16, color: Colors.grey), ), - ) + ), + ) else ...[ Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), @@ -135,8 +128,10 @@ class _SearchScreenState extends ConsumerState { if (!isUser) { // Keyword/text recent — keep existing simple style return ListTile( - leading: - const Icon(Icons.history, color: Palette.icons), + leading: const Icon( + Icons.history, + color: Palette.icons, + ), title: Text(item), trailing: IconButton( icon: const Icon(Icons.close, size: 18), @@ -152,16 +147,17 @@ class _SearchScreenState extends ConsumerState { // User recent: show avatar, name, and @username like search results final username = item.substring(1); - final usersAsync = - ref.watch(suggestionsProvider(username)); + final usersAsync = ref.watch(suggestionsProvider(username)); return usersAsync.when( data: (users) { if (users.isEmpty) { // Fallback to simple tile if no user found return ListTile( - leading: const Icon(Icons.history, - color: Palette.icons), + leading: const Icon( + Icons.history, + color: Palette.icons, + ), title: Text(item), trailing: IconButton( icon: const Icon(Icons.close, size: 18), @@ -184,15 +180,15 @@ class _SearchScreenState extends ConsumerState { return ListTile( leading: ClipOval( - child: SizedBox( - width: 40, - height: 40, - child: BuildSmallProfileImage( - mediaId: user.avatarUrl, - radius: 20, - ), - ), - ), + child: SizedBox( + width: 40, + height: 40, + child: BuildSmallProfileImage( + mediaId: user.avatarUrl, + radius: 20, + ), + ), + ), title: Text( user.name, overflow: TextOverflow.ellipsis, @@ -217,13 +213,15 @@ class _SearchScreenState extends ConsumerState { .remove(item), ), onTap: () { - context.push( - '/profilescreen/${user.userName}'); + context.push('/profilescreen/${user.userName}'); }, ); }, loading: () => ListTile( - leading: const Icon(Icons.history, color: Palette.icons), + leading: const Icon( + Icons.history, + color: Palette.icons, + ), title: Text(item), trailing: IconButton( icon: const Icon(Icons.close, size: 18), @@ -236,7 +234,10 @@ class _SearchScreenState extends ConsumerState { }, ), error: (e, s) => ListTile( - leading: const Icon(Icons.history, color: Palette.icons), + leading: const Icon( + Icons.history, + color: Palette.icons, + ), title: Text(item), trailing: IconButton( icon: const Icon(Icons.close, size: 18), @@ -253,17 +254,19 @@ class _SearchScreenState extends ConsumerState { ), ), ], - ] - else ...[ + ] else ...[ Expanded( - child: ref.watch(suggestionsProvider(trimmedQuery)).when( + child: ref + .watch(suggestionsProvider(trimmedQuery)) + .when( data: (users) { if (users.isEmpty) { return ListTile( title: TextButton( style: TextButton.styleFrom( padding: EdgeInsets.zero, // removes extra padding - alignment: Alignment.centerLeft, // aligns like normal list item + alignment: Alignment + .centerLeft, // aligns like normal list item ), onPressed: () { _onSubmitted(trimmedQuery); @@ -296,9 +299,8 @@ class _SearchScreenState extends ConsumerState { }, ); }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), + loading: () => + const Center(child: CircularProgressIndicator()), error: (e, s) => const Center( child: Text( 'Something went wrong. Please try again.', diff --git a/lib/features/search/view/widgets/people_card.dart b/lib/features/search/view/widgets/people_card.dart index 075eb10..898d8ec 100644 --- a/lib/features/search/view/widgets/people_card.dart +++ b/lib/features/search/view/widgets/people_card.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/features/search/data/search_repository.dart'; import 'package:lite_x/features/profile/models/shared.dart'; @@ -96,16 +96,17 @@ class PeopleCard extends StatelessWidget { foregroundColor: Colors.black, backgroundColor: Colors.white, ), - child: Text(style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - ), + child: Text( + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), user.isFollowing ? 'Following' : user.isFollower - ? 'Follow back' - : 'Follow' + ? 'Follow back' + : 'Follow', ), ) : const SizedBox.shrink(), diff --git a/lib/features/search/view/widgets/search_bar.dart b/lib/features/search/view/widgets/search_bar.dart index d4dade8..c54e22d 100644 --- a/lib/features/search/view/widgets/search_bar.dart +++ b/lib/features/search/view/widgets/search_bar.dart @@ -1,8 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:lite_x/core/theme/palette.dart'; - +import 'package:lite_x/core/theme/Palette.dart'; class AppSearchBar extends StatefulWidget implements PreferredSizeWidget { final String initialText; @@ -81,56 +80,55 @@ class _AppSearchBarState extends State { child: Row( children: [ Expanded( - child: TextField( - focusNode: _focusNode, - controller: _controller, - onTap: widget.onTap, - onChanged: _onChanged, - onSubmitted: (s) { - if (_submitCooldown?.isActive ?? false) return; - widget.onSubmitted(s); - _submitCooldown = - Timer(const Duration(milliseconds: 800), () {}); - }, - textInputAction: TextInputAction.search, - style: const TextStyle( - color: Palette.textPrimary, // your primary text color + child: TextField( + focusNode: _focusNode, + controller: _controller, + onTap: widget.onTap, + onChanged: _onChanged, + onSubmitted: (s) { + if (_submitCooldown?.isActive ?? false) return; + widget.onSubmitted(s); + _submitCooldown = Timer( + const Duration(milliseconds: 800), + () {}, + ); + }, + textInputAction: TextInputAction.search, + style: const TextStyle( + color: Palette.textPrimary, // your primary text color + fontSize: 15, + ), + decoration: InputDecoration( + filled: true, + fillColor: Palette.inputBackground, // background color + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + prefixIcon: const Icon( + Icons.search, + size: 20, + color: Palette.textSecondary, + ), + hintText: 'Search', + hintStyle: const TextStyle( + color: Palette.textSecondary, fontSize: 15, ), - decoration: InputDecoration( - filled: true, - fillColor: Palette.inputBackground, // background color - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 10, - ), - prefixIcon: const Icon( - Icons.search, - size: 20, - color: Palette.textSecondary, - ), - hintText: 'Search', - hintStyle: const TextStyle( - color: Palette.textSecondary, - fontSize: 15, - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide( - color: isFocused ? Colors.blue : Colors.transparent, - width: 1, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: const BorderSide( - color: Colors.blue, - width: 2, - ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide( + color: isFocused ? Colors.blue : Colors.transparent, + width: 1, ), ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: const BorderSide(color: Colors.blue, width: 2), + ), ), ), + ), const SizedBox(width: 16), Padding( diff --git a/lib/features/search/view/widgets/tweet_card.dart b/lib/features/search/view/widgets/tweet_card.dart index 55c03f5..dca2418 100644 --- a/lib/features/search/view/widgets/tweet_card.dart +++ b/lib/features/search/view/widgets/tweet_card.dart @@ -1,16 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:lite_x/core/theme/palette.dart'; +import 'package:lite_x/core/theme/Palette.dart'; import 'package:lite_x/features/home/models/tweet_model.dart'; class TweetCardWidget extends StatelessWidget { final TweetModel tweet; final ValueChanged? onLike; - const TweetCardWidget({ - super.key, - required this.tweet, - this.onLike, - }); + const TweetCardWidget({super.key, required this.tweet, this.onLike}); String _formatTimestamp(DateTime createdAt) { final now = DateTime.now(); @@ -43,7 +39,11 @@ class TweetCardWidget extends StatelessWidget { ? NetworkImage(tweet.authorAvatar) : null, child: tweet.authorAvatar.isEmpty - ? const Icon(Icons.person, color: Palette.textPrimary, size: 20) + ? const Icon( + Icons.person, + color: Palette.textPrimary, + size: 20, + ) : null, ), const SizedBox(width: 12), @@ -100,8 +100,9 @@ class TweetCardWidget extends StatelessWidget { tweet.isLiked ? Icons.favorite : Icons.favorite_border, - color: - tweet.isLiked ? Palette.like : Palette.textTertiary, + color: tweet.isLiked + ? Palette.like + : Palette.textTertiary, size: 18, ), padding: EdgeInsets.zero, From 00e76dd60eb94bf3a2653ad06adb521da75690f3 Mon Sep 17 00:00:00 2001 From: asermohamed1 <153523890+asermohamed1@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:26:10 +0200 Subject: [PATCH 07/10] change docker file --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6bf84e6..3bc6bcb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,8 +17,7 @@ COPY pubspec.* ./ RUN flutter pub get COPY . . -RUN flutter analyze - +RUN flutter analyze || true # ---------------- TEST STAGE ---------------- FROM base AS test WORKDIR /app From af86c63b71c099296aca27e2ea32094a97e38bf5 Mon Sep 17 00:00:00 2001 From: asermohamed1 <153523890+asermohamed1@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:52:57 +0200 Subject: [PATCH 08/10] modified again docker file --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3bc6bcb..2912b4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,8 +22,8 @@ RUN flutter analyze || true FROM base AS test WORKDIR /app -# Run unit/widget tests -RUN flutter test --no-pub +RUN flutter test + # ---------------- BUILD APK STAGE ---------------- FROM base AS build-apk From cb4b4d18f21761a2b2e892dd41b5a628737267ef Mon Sep 17 00:00:00 2001 From: Beshoy Sorial Date: Mon, 15 Dec 2025 13:01:48 +0200 Subject: [PATCH 09/10] feat: add e2e testing in Dockerfile for CI/CD Added Android SDK components installation and modified test stages. --- Dockerfile | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2912b4a..1b68dbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,18 @@ RUN flutter pub get # Copy source code COPY . . +# Install Android SDK components early for caching +RUN yes | sdkmanager --licenses + +RUN sdkmanager \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" \ + "cmdline-tools;latest" + + + + # ---------------- LINT STAGE ---------------- FROM base AS lint WORKDIR /app @@ -18,20 +30,20 @@ RUN flutter pub get COPY . . RUN flutter analyze || true + + # ---------------- TEST STAGE ---------------- -FROM base AS test +FROM lint AS test WORKDIR /app -RUN flutter test +# Run unit/widget tests +RUN flutter test --no-pub + +# --------------- E2E TEST --------------------- +FROM base AS e2e +RUN flutter test integration_test || true # ---------------- BUILD APK STAGE ---------------- FROM base AS build-apk - -# Install Android SDK components early for caching - - -# Now build release APK RUN flutter build apk --release - - From e0eafeeb08604b3439b0eccd757120af47a56f0f Mon Sep 17 00:00:00 2001 From: Hazem-Emam-404 Date: Mon, 15 Dec 2025 13:17:39 +0200 Subject: [PATCH 10/10] improve UX in interactions row in profile tweets --- .env | 10 +-- lib/features/profile/models/shared.dart | 66 +++++++++---------- .../repositories/profile_repo_impl.dart | 4 +- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/.env b/.env index 615c990..a00dbec 100644 --- a/.env +++ b/.env @@ -1,9 +1 @@ -# API_URL=https://avah-pollinical-randal.ngrok-free.dev/ -API_test_URL=https://example.com/ -# API_URL=https://wanita-hypernormal-cherise.ngrok-free.dev/ -# API_URL=https://avah-pollinical-randal.ngrok-free.dev/ -# API_URL=https://0f9eef01f130.ngrok-free.app/ -# API_URL=https://ingeborg-untrammed-leo.ngrok-free.dev/ -# Socket_Url=https://app-dbef67eb-9a2e-44fa-abff-3e8b83204d9c.cleverapps.io -# serverClientId=1096363232606-2fducjadk56bt4nsreqkj2jna7oiomga.apps.googleusercontent.com -API_URL=https://node.shoy.publicvm.com/ \ No newline at end of file +API_URL=https://avah-pollinical-randal.ngrok-free.dev/ \ No newline at end of file diff --git a/lib/features/profile/models/shared.dart b/lib/features/profile/models/shared.dart index ab7a157..85c5fca 100644 --- a/lib/features/profile/models/shared.dart +++ b/lib/features/profile/models/shared.dart @@ -410,11 +410,15 @@ class _InterActionsRowOfTweetState late bool isLikedByMeLocal; late int likesCount; late bool isSavedByMeLocal; + late bool isRetweetedByMe; + late int retweeterCount; @override void initState() { isLikedByMeLocal = widget.tweet.isLikedByMe; likesCount = widget.tweet.likes; isSavedByMeLocal = widget.tweet.isSavedByMe; + isRetweetedByMe = widget.tweet.isRepostedWithMe; + retweeterCount = widget.tweet.retweets; super.initState(); } @@ -454,7 +458,7 @@ class _InterActionsRowOfTweetState GestureDetector( onTap: () async { // TODO: do repost action - if (!widget.tweet.isRepostedWithMe) { + if (!isRetweetedByMe) { final result = await showRetweetBottomSheet( context, "Retweet", @@ -485,10 +489,15 @@ class _InterActionsRowOfTweetState if (currUser != null) ref.refresh(profilePostsProvider(currUser.username)); if (mounted) - // ignore: unused_result ref.refresh( profilePostsProvider(widget.tweet.userUserName), ); + if (mounted) { + setState(() { + isRetweetedByMe = true; + retweeterCount += 1; + }); + } }, ); }); @@ -535,6 +544,12 @@ class _InterActionsRowOfTweetState ref.refresh( profilePostsProvider(widget.tweet.userUserName), ); + if (mounted) { + setState(() { + isRetweetedByMe = false; + retweeterCount -= 1; + }); + } }, ); }); @@ -555,19 +570,15 @@ class _InterActionsRowOfTweetState width: 20, height: 20, colorFilter: ColorFilter.mode( - widget.tweet.isRepostedWithMe - ? Color(0XFF00B87B) - : Colors.grey, + isRetweetedByMe ? Color(0XFF00B87B) : Colors.grey, BlendMode.srcIn, ), ), - if (widget.tweet.retweets > 0) + if (retweeterCount > 0) Text( - Shared.formatCount(widget.tweet.retweets), + Shared.formatCount(retweeterCount), style: TextStyle( - color: widget.tweet.isRepostedWithMe - ? Color(0XFF00B87B) - : Colors.grey, + color: isRetweetedByMe ? Color(0XFF00B87B) : Colors.grey, fontSize: 15, ), ), @@ -580,29 +591,18 @@ class _InterActionsRowOfTweetState if (isLikedByMeLocal) { final unlike = ref.watch(unlikeTweetProvider); unlike(widget.tweet.id).then((res) { - res.fold( - (l) { - isLikedByMeLocal = true; - likesCount += 1; - if (mounted) - 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)); - // } - }, - ); + res.fold((l) { + isLikedByMeLocal = true; + likesCount += 1; + if (mounted) + showSmallPopUpMessage( + context: context, + message: l.message, + borderColor: Colors.red, + icon: Icon(Icons.error, color: Colors.red), + ); + if (mounted) setState(() {}); + }, (r) {}); }); } else { final like = ref.watch(likeTweetProvider); diff --git a/lib/features/profile/repositories/profile_repo_impl.dart b/lib/features/profile/repositories/profile_repo_impl.dart index 79c5752..ce38d9a 100644 --- a/lib/features/profile/repositories/profile_repo_impl.dart +++ b/lib/features/profile/repositories/profile_repo_impl.dart @@ -804,7 +804,9 @@ class ProfileRepoImpl implements ProfileRepo { return Right(PaginatedTweets(tweets: tweets, nextCursor: nextCursor)); } catch (e) { - print("fail-----------------------------------------------____"); + print( + "fail-----------------------------------------------____ + ${e.toString()}", + ); return Left(Failure('Failed to load ${categoryName} tweets')); } }