diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index e88b2a3fd..5eec9f6ab 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -19,8 +19,8 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'flutter/samples' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 91e222a7b..ce59d6f27 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -22,8 +22,8 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/gemini-cli.yml b/.github/workflows/gemini-cli.yml index b4feae184..8f8b1c23a 100644 --- a/.github/workflows/gemini-cli.yml +++ b/.github/workflows/gemini-cli.yml @@ -144,7 +144,7 @@ jobs: - name: 'Checkout PR branch' if: |- ${{ steps.get_context.outputs.is_pr == 'true' }} - uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # ratchet:actions/checkout@v4 + uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # ratchet:actions/checkout@v4 with: token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}' repository: '${{ github.repository }}' @@ -154,7 +154,7 @@ jobs: - name: 'Checkout main branch' if: |- ${{ steps.get_context.outputs.is_pr == 'false' }} - uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # ratchet:actions/checkout@v4 + uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # ratchet:actions/checkout@v4 with: token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}' repository: '${{ github.repository }}' diff --git a/.github/workflows/gemini-issue-automated-triage.yml b/.github/workflows/gemini-issue-automated-triage.yml index 9bc4ec054..c122f78b5 100644 --- a/.github/workflows/gemini-issue-automated-triage.yml +++ b/.github/workflows/gemini-issue-automated-triage.yml @@ -44,7 +44,7 @@ jobs: steps: - name: 'Checkout repository' - uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # ratchet:actions/checkout@v4 + uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # ratchet:actions/checkout@v4 - name: 'Generate GitHub App Token' id: 'generate_token' diff --git a/.github/workflows/gemini-issue-scheduled-triage.yml b/.github/workflows/gemini-issue-scheduled-triage.yml index 2856ce4d2..bda1b89e6 100644 --- a/.github/workflows/gemini-issue-scheduled-triage.yml +++ b/.github/workflows/gemini-issue-scheduled-triage.yml @@ -26,7 +26,7 @@ jobs: steps: - name: 'Checkout repository' - uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # ratchet:actions/checkout@v4 + uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # ratchet:actions/checkout@v4 - name: 'Generate GitHub App Token' id: 'generate_token' diff --git a/.github/workflows/gemini-pr-review.yml b/.github/workflows/gemini-pr-review.yml index e9ae2a48d..d8412354c 100644 --- a/.github/workflows/gemini-pr-review.yml +++ b/.github/workflows/gemini-pr-review.yml @@ -66,7 +66,7 @@ jobs: steps: - name: 'Checkout PR code' - uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # ratchet:actions/checkout@v4 + uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # ratchet:actions/checkout@v4 - name: 'Generate GitHub App Token' id: 'generate_token' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 689aa71fa..80c849059 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,8 @@ jobs: flutter_version: [stable] os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 with: distribution: 'zulu' java-version: '17' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..3bb4049a4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +Notable changes to the samples in this repository are listed here. + +## [Unreleased] + +### compass_app + +* Scope `LogoutViewModel` to the home route so it is not recreated on every + `HomeHeader` rebuild ([#2604](https://github.com/flutter/samples/issues/2604)). diff --git a/compass_app/app/lib/routing/router.dart b/compass_app/app/lib/routing/router.dart index 1ed94ed02..05cad62b0 100644 --- a/compass_app/app/lib/routing/router.dart +++ b/compass_app/app/lib/routing/router.dart @@ -11,10 +11,10 @@ import '../ui/activities/view_models/activities_viewmodel.dart'; import '../ui/activities/widgets/activities_screen.dart'; import '../ui/auth/login/view_models/login_viewmodel.dart'; import '../ui/auth/login/widgets/login_screen.dart'; +import '../ui/auth/logout/view_models/logout_viewmodel.dart'; import '../ui/booking/view_models/booking_viewmodel.dart'; import '../ui/booking/widgets/booking_screen.dart'; -import '../ui/home/view_models/home_viewmodel.dart'; -import '../ui/home/widgets/home_screen.dart'; +import '../ui/home/widgets/home_screen_container.dart'; import '../ui/results/view_models/results_viewmodel.dart'; import '../ui/results/widgets/results_screen.dart'; import '../ui/search_form/view_models/search_form_viewmodel.dart'; @@ -46,7 +46,14 @@ GoRouter router(AuthRepository authRepository) => GoRouter( bookingRepository: context.read(), userRepository: context.read(), ); - return HomeScreen(viewModel: viewModel); + final logoutViewModel = LogoutViewModel( + authRepository: context.read(), + itineraryConfigRepository: context.read(), + ); + return HomeScreen( + viewModel: viewModel, + logoutViewModel: logoutViewModel, + ); }, routes: [ GoRoute( diff --git a/compass_app/app/lib/ui/activities/widgets/activity_entry.dart b/compass_app/app/lib/ui/activities/widgets/activity_entry.dart index 300e560a7..b561844f9 100644 --- a/compass_app/app/lib/ui/activities/widgets/activity_entry.dart +++ b/compass_app/app/lib/ui/activities/widgets/activity_entry.dart @@ -30,6 +30,7 @@ class ActivityEntry extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(8), child: CachedNetworkImage( + errorWidget: (context, url, error) => _ActivityErrorPlaceholder(), imageUrl: activity.imageUrl, height: 80, width: 80, @@ -66,3 +67,23 @@ class ActivityEntry extends StatelessWidget { ); } } + +class _ActivityErrorPlaceholder extends StatelessWidget { + const _ActivityErrorPlaceholder(); + + @override + Widget build(BuildContext context) { + return Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.broken_image_outlined, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ); + } +} diff --git a/compass_app/app/lib/ui/home/widgets/home_screen.dart b/compass_app/app/lib/ui/home/widgets/home_screen.dart index fefe78003..09d1e49ba 100644 --- a/compass_app/app/lib/ui/home/widgets/home_screen.dart +++ b/compass_app/app/lib/ui/home/widgets/home_screen.dart @@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart'; import '../../../domain/models/booking/booking_summary.dart'; import '../../../routing/routes.dart'; +import '../../auth/logout/view_models/logout_viewmodel.dart'; import '../../core/localization/applocalization.dart'; import '../../core/themes/colors.dart'; import '../../core/themes/dimens.dart'; @@ -18,9 +19,14 @@ import 'home_title.dart'; const String bookingButtonKey = 'booking-button'; class HomeScreen extends StatefulWidget { - const HomeScreen({super.key, required this.viewModel}); + const HomeScreen({ + super.key, + required this.viewModel, + required this.logoutViewModel, + }); final HomeViewModel viewModel; + final LogoutViewModel logoutViewModel; @override State createState() => _HomeScreenState(); @@ -88,7 +94,10 @@ class _HomeScreenState extends State { vertical: Dimens.of(context).paddingScreenVertical, horizontal: Dimens.of(context).paddingScreenHorizontal, ), - child: HomeHeader(viewModel: widget.viewModel), + child: HomeHeader( + viewModel: widget.viewModel, + logoutViewModel: widget.logoutViewModel, + ), ), ), SliverList.builder( diff --git a/compass_app/app/lib/ui/home/widgets/home_screen_container.dart b/compass_app/app/lib/ui/home/widgets/home_screen_container.dart new file mode 100644 index 000000000..c62fbc3c1 --- /dev/null +++ b/compass_app/app/lib/ui/home/widgets/home_screen_container.dart @@ -0,0 +1,39 @@ +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +import '../../../data/repositories/booking/booking_repository.dart'; +import '../../../data/repositories/user/user_repository.dart'; +import '../view_models/home_viewmodel.dart'; +import 'home_screen.dart'; + +class HomeScreenContainer extends StatefulWidget { + const HomeScreenContainer({super.key}); + + @override + State createState() => _HomeScreenContainerState(); +} + +class _HomeScreenContainerState extends State { + late final HomeViewModel _viewModel; + + @override + void initState() { + super.initState(); + + _viewModel = HomeViewModel( + bookingRepository: context.read(), + userRepository: context.read(), + ); + } + + @override + Widget build(BuildContext context) { + return HomeScreen(viewModel: _viewModel); + } + + @override + void dispose() { + _viewModel.dispose(); + super.dispose(); + } +} diff --git a/compass_app/app/lib/ui/home/widgets/home_title.dart b/compass_app/app/lib/ui/home/widgets/home_title.dart index 165e7ffe5..16755793a 100644 --- a/compass_app/app/lib/ui/home/widgets/home_title.dart +++ b/compass_app/app/lib/ui/home/widgets/home_title.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:provider/provider.dart'; import '../../auth/logout/view_models/logout_viewmodel.dart'; import '../../auth/logout/widgets/logout_button.dart'; @@ -13,9 +12,14 @@ import '../../core/themes/dimens.dart'; import '../view_models/home_viewmodel.dart'; class HomeHeader extends StatelessWidget { - const HomeHeader({super.key, required this.viewModel}); + const HomeHeader({ + super.key, + required this.viewModel, + required this.logoutViewModel, + }); final HomeViewModel viewModel; + final LogoutViewModel logoutViewModel; @override Widget build(BuildContext context) { @@ -37,12 +41,7 @@ class HomeHeader extends StatelessWidget { height: Dimens.of(context).profilePictureSize, ), ), - LogoutButton( - viewModel: LogoutViewModel( - authRepository: context.read(), - itineraryConfigRepository: context.read(), - ), - ), + LogoutButton(viewModel: logoutViewModel), ], ), const SizedBox(height: Dimens.paddingVertical), diff --git a/compass_app/app/test/ui/home/widgets/home_screen_test.dart b/compass_app/app/test/ui/home/widgets/home_screen_test.dart index 24890be41..a99981100 100644 --- a/compass_app/app/test/ui/home/widgets/home_screen_test.dart +++ b/compass_app/app/test/ui/home/widgets/home_screen_test.dart @@ -2,16 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:compass_app/data/repositories/auth/auth_repository.dart'; -import 'package:compass_app/data/repositories/itinerary_config/itinerary_config_repository.dart'; import 'package:compass_app/routing/routes.dart'; +import 'package:compass_app/ui/auth/logout/view_models/logout_viewmodel.dart'; import 'package:compass_app/ui/home/view_models/home_viewmodel.dart'; import 'package:compass_app/ui/home/widgets/home_screen.dart'; import 'package:compass_app/utils/result.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:provider/provider.dart'; import '../../../../testing/app.dart'; import '../../../../testing/fakes/repositories/fake_auth_repository.dart'; @@ -24,6 +22,7 @@ import '../../../../testing/models/booking.dart'; void main() { group('HomeScreen tests', () { late HomeViewModel viewModel; + late LogoutViewModel logoutViewModel; late MockGoRouter goRouter; late FakeBookingRepository bookingRepository; @@ -33,6 +32,10 @@ void main() { bookingRepository: bookingRepository, userRepository: FakeUserRepository(), ); + logoutViewModel = LogoutViewModel( + authRepository: FakeAuthRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); goRouter = MockGoRouter(); when(() => goRouter.push(any())).thenAnswer((_) => Future.value(null)); }); @@ -40,12 +43,9 @@ void main() { Future loadWidget(WidgetTester tester) async { await testApp( tester, - ChangeNotifierProvider.value( - value: FakeAuthRepository() as AuthRepository, - child: Provider.value( - value: FakeItineraryConfigRepository() as ItineraryConfigRepository, - child: HomeScreen(viewModel: viewModel), - ), + HomeScreen( + viewModel: viewModel, + logoutViewModel: logoutViewModel, ), goRouter: goRouter, ); diff --git a/cupertino_gallery/lib/widgets/sheet_page.dart b/cupertino_gallery/lib/widgets/sheet_page.dart index 9d27c0bd2..103d1bdcc 100644 --- a/cupertino_gallery/lib/widgets/sheet_page.dart +++ b/cupertino_gallery/lib/widgets/sheet_page.dart @@ -13,25 +13,20 @@ class SheetPage extends StatelessWidget { onPressed: () { Navigator.of(context).push( CupertinoSheetRoute( - scrollableBuilder: - (BuildContext context, ScrollController controller) { - Widget widgetBuilder(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: const Text('Sheet'), - trailing: GestureDetector( - child: const Icon(CupertinoIcons.xmark), - onTap: () { - Navigator.of(context).pop(); - }, - ), - ), - child: const Center(child: Text('This is a sheet')), - ); - } - - return widgetBuilder(context); - }, + builder: (BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Sheet'), + trailing: GestureDetector( + child: const Icon(CupertinoIcons.xmark), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ), + child: const Center(child: Text('This is a sheet')), + ); + }, ), ); }, diff --git a/simple_sdf/lib/main.dart b/simple_sdf/lib/main.dart index 214c68783..3d5ec85f9 100644 --- a/simple_sdf/lib/main.dart +++ b/simple_sdf/lib/main.dart @@ -44,13 +44,15 @@ class MyHomePage extends StatelessWidget { } class ShaderPainter extends CustomPainter { - ShaderPainter({required this.shader}); - ui.FragmentShader shader; + ShaderPainter({required this.shader}) + : _resolution = shader.getUniformVec2('resolution'); + + final ui.FragmentShader shader; + final ui.UniformVec2Slot _resolution; @override void paint(Canvas canvas, Size size) { - shader.setFloat(0, size.width); - shader.setFloat(1, size.height); + _resolution.set(size.width, size.height); final paint = Paint()..shader = shader; canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); diff --git a/simple_shader/lib/main.dart b/simple_shader/lib/main.dart index dea7caa88..037eb69b4 100644 --- a/simple_shader/lib/main.dart +++ b/simple_shader/lib/main.dart @@ -44,13 +44,15 @@ class MyHomePage extends StatelessWidget { } class ShaderPainter extends CustomPainter { - ShaderPainter({required this.shader}); - ui.FragmentShader shader; + ShaderPainter({required this.shader}) + : _resolution = shader.getUniformVec2('resolution'); + + final ui.FragmentShader shader; + final ui.UniformVec2Slot _resolution; @override void paint(Canvas canvas, Size size) { - shader.setFloat(0, size.width); - shader.setFloat(1, size.height); + _resolution.set(size.width, size.height); final paint = Paint()..shader = shader; canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); diff --git a/web_embedding/ng-flutter/package.json b/web_embedding/ng-flutter/package.json index ae9112762..f42e6436b 100644 --- a/web_embedding/ng-flutter/package.json +++ b/web_embedding/ng-flutter/package.json @@ -11,24 +11,24 @@ }, "private": true, "dependencies": { - "@angular/animations": "^21.1.1", - "@angular/cdk": "^21.1.2", - "@angular/common": "^21.1.1", - "@angular/compiler": "^21.1.0", - "@angular/core": "^21.2.0", - "@angular/forms": "^21.1.0", - "@angular/material": "^21.1.1", - "@angular/platform-browser": "^21.2.0", - "@angular/platform-browser-dynamic": "^21.1.2", - "@angular/router": "^21.1.0", + "@angular/animations": "^22.0.2", + "@angular/cdk": "^22.0.0", + "@angular/common": "^22.0.1", + "@angular/compiler": "^22.0.2", + "@angular/core": "^22.0.2", + "@angular/forms": "^22.0.0", + "@angular/material": "^22.0.2", + "@angular/platform-browser": "^22.0.1", + "@angular/platform-browser-dynamic": "^22.0.0", + "@angular/router": "^22.0.1", "rxjs": "~7.8.1", "tslib": "^2.6.2", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^21.1.2", - "@angular/cli": "~21.1.1", - "@angular/compiler-cli": "^21.2.0", + "@angular-devkit/build-angular": "^22.0.1", + "@angular/cli": "~22.0.2", + "@angular/compiler-cli": "^22.0.1", "@types/jasmine": "~6.0.0", "jasmine-core": "~6.0.0", "karma": "~6.4.2",