Merge branch 'main' into migrate-uiscene-ios-books

pull/2845/head
Eric Windmill 3 weeks ago committed by GitHub
commit 407e5a4ebf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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'

@ -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'

@ -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 }}'

@ -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'

@ -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'

@ -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'

@ -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'

@ -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)).

@ -65,10 +65,12 @@ class _MyHomePageState extends State<MyHomePage> {
// Write the value to [SharedPreferences] which will get read on the
// [SimpleDatabase]'s isolate. For this example the value is always true
// just for demonstration purposes.
final Future<void> sharedPreferencesSet = SharedPreferences.getInstance()
.then(
(sharedPreferences) => sharedPreferences.setBool('isDebug', true),
);
final SharedPreferencesAsync sharedPreferences = SharedPreferencesAsync();
final Future<void> sharedPreferencesSet = sharedPreferences.setBool(
'isDebug',
true,
);
final Future<Directory> tempDirFuture = path_provider
.getTemporaryDirectory();

@ -30,7 +30,7 @@ void main() {
setUpAll(() async {
// Clear any stored shared preferences
final sharedPreferences = await SharedPreferences.getInstance();
final sharedPreferences = SharedPreferencesAsync();
await sharedPreferences.clear();
// Start the dart server

@ -13,9 +13,11 @@ class SharedPreferencesService {
Future<Result<String?>> fetchToken() async {
try {
final sharedPreferences = await SharedPreferences.getInstance();
final sharedPreferences = SharedPreferencesAsync();
_log.finer('Got token from SharedPreferences');
return Result.ok(sharedPreferences.getString(_tokenKey));
return Result.ok(await sharedPreferences.getString(_tokenKey));
} on Exception catch (e) {
_log.warning('Failed to get token', e);
return Result.error(e);
@ -24,14 +26,18 @@ class SharedPreferencesService {
Future<Result<void>> saveToken(String? token) async {
try {
final sharedPreferences = await SharedPreferences.getInstance();
final sharedPreferences = SharedPreferencesAsync();
if (token == null) {
_log.finer('Removed token');
await sharedPreferences.remove(_tokenKey);
} else {
_log.finer('Replaced token');
await sharedPreferences.setString(_tokenKey, token);
}
return const Result.ok(null);
} on Exception catch (e) {
_log.warning('Failed to set token', e);

@ -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';
@ -42,11 +42,13 @@ GoRouter router(AuthRepository authRepository) => GoRouter(
GoRoute(
path: Routes.home,
builder: (context, state) {
final viewModel = HomeViewModel(
bookingRepository: context.read(),
userRepository: context.read(),
final logoutViewModel = LogoutViewModel(
authRepository: context.read(),
itineraryConfigRepository: context.read(),
);
return HomeScreenContainer(
logoutViewModel: logoutViewModel,
);
return HomeScreen(viewModel: viewModel);
},
routes: [
GoRoute(

@ -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,
),
);
}
}

@ -65,31 +65,27 @@ class HomeViewModel extends ChangeNotifier {
}
Future<Result<void>> _deleteBooking(int id) async {
try {
final resultDelete = await _bookingRepository.delete(id);
switch (resultDelete) {
case Ok<void>():
_log.fine('Deleted booking $id');
case Error<void>():
_log.warning('Failed to delete booking $id', resultDelete.error);
return resultDelete;
}
// After deleting the booking, we need to reload the bookings list.
// BookingRepository is the source of truth for bookings.
final resultLoadBookings = await _bookingRepository.getBookingsList();
switch (resultLoadBookings) {
case Ok<List<BookingSummary>>():
_bookings = resultLoadBookings.value;
_log.fine('Loaded bookings');
case Error<List<BookingSummary>>():
_log.warning('Failed to load bookings', resultLoadBookings.error);
return resultLoadBookings;
}
final resultDelete = await _bookingRepository.delete(id);
switch (resultDelete) {
case Ok<void>():
_log.fine('Deleted booking $id');
case Error<void>():
_log.warning('Failed to delete booking $id', resultDelete.error);
return resultDelete;
}
return resultLoadBookings;
} finally {
notifyListeners();
// After deleting the booking, reload the bookings list.
final resultLoadBookings = await _bookingRepository.getBookingsList();
switch (resultLoadBookings) {
case Ok<List<BookingSummary>>():
_bookings = resultLoadBookings.value;
_log.fine('Loaded bookings');
notifyListeners(); // notify only when data changes
case Error<List<BookingSummary>>():
_log.warning('Failed to load bookings', resultLoadBookings.error);
return resultLoadBookings;
}
return resultLoadBookings;
}
}

@ -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<HomeScreen> createState() => _HomeScreenState();
@ -88,7 +94,10 @@ class _HomeScreenState extends State<HomeScreen> {
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(

@ -0,0 +1,48 @@
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 '../../auth/logout/view_models/logout_viewmodel.dart';
import '../view_models/home_viewmodel.dart';
import 'home_screen.dart';
class HomeScreenContainer extends StatefulWidget {
const HomeScreenContainer({
super.key,
required this.logoutViewModel,
});
final LogoutViewModel logoutViewModel;
@override
State<HomeScreenContainer> createState() => _HomeScreenContainerState();
}
class _HomeScreenContainerState extends State<HomeScreenContainer> {
late final HomeViewModel _viewModel;
@override
void initState() {
super.initState();
_viewModel = HomeViewModel(
bookingRepository: context.read<BookingRepository>(),
userRepository: context.read<UserRepository>(),
);
}
@override
Widget build(BuildContext context) {
return HomeScreen(
viewModel: _viewModel,
logoutViewModel: widget.logoutViewModel,
);
}
@override
void dispose() {
_viewModel.dispose();
super.dispose();
}
}

@ -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),

@ -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<void> 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,
);

@ -13,25 +13,20 @@ class SheetPage extends StatelessWidget {
onPressed: () {
Navigator.of(context).push(
CupertinoSheetRoute<void>(
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')),
);
},
),
);
},

@ -45993,9 +45993,7 @@ class ObjCBlock_ffiVoid_ObjCObject_ffiUnsignedLong_bool extends _ObjCBlockBase {
ffi.UnsignedLong,
ffi.Pointer<ffi.Bool>,
)
>(
_ObjCBlock_ffiVoid_ObjCObject_ffiUnsignedLong_bool_closureTrampoline,
)
>(_ObjCBlock_ffiVoid_ObjCObject_ffiUnsignedLong_bool_closureTrampoline)
.cast(),
_ObjCBlock_ffiVoid_ObjCObject_ffiUnsignedLong_bool_registerClosure(
(

@ -50,8 +50,8 @@ skip_ci:
- add_to_app/multiple_flutters/multiple_flutters_module
- add_to_app/plugin/flutter_module_using_plugin
- add_to_app/prebuilt_module/flutter_module
# remove after 3.44 release
- cupertino_gallery
skip_ci_beta:
- material_3_demo
# - sample_name_to_skip

@ -1,13 +1,16 @@
import UIKit
import Flutter
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@ -24,6 +26,29 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@ -43,9 +68,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

@ -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",

Loading…
Cancel
Save