diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 3abe725bf..722ed96c8 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -12,7 +12,7 @@ match any new language/SDK features, etc.).
| infinite_list | filiph | 5/13/20 |
| isolate_example | johnpryan | 11/21/19 |
| jsonexample | redbrogdon | 1/3/20 |
-| place_tracker | | |
+| place_tracker | johnpryan | 8/13/20 |
| platform_channels | | |
| platform_design | johnpryan | 10/7/19 |
| platform_view_swift | redbrogdon | 10/7/19 |
diff --git a/place_tracker/README.md b/place_tracker/README.md
index 4f396dab2..0b9fe3d49 100644
--- a/place_tracker/README.md
+++ b/place_tracker/README.md
@@ -17,14 +17,16 @@ of building it out. This sample currently only works on Android (see Caveat belo
### `place_map.dart`
-This page shows a full-screen GoogleMap widget with place markers. Provides examples of how
-to stack other widgets on top of a GoogleMap widget, how to add markers to a map, and how to make
-other flutter widgets interact with the GoogleMap widget.
+This page shows a full-screen GoogleMap widget with place markers. Provides
+examples of how to stack other widgets on top of a GoogleMap widget, how to add
+markers to a map, and how to make other flutter widgets interact with the
+GoogleMap widget.
### `place_details.dart`
-This page shows a detailed view of a single place. Provides examples of how to place a
-GoogleMap widget inside of a ListView and how to disable certain touch gestures on the map.
+This page shows a detailed view of a single place. Provides examples of how to
+place a GoogleMap widget inside of a ListView and how to disable certain touch
+gestures on the map.
## Getting Started
@@ -32,7 +34,8 @@ To run this sample app, you will need an API key.
Get an API key at .
-Specify your API key in the application manifest `android/app/src/main/AndroidManifest.xml`:
+Specify your API key in the application manifest
+`android/app/src/main/AndroidManifest.xml`:
```xml
```
-For additional help setting up the plugin, see the plugin's [README](https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter) page.
+For additional help setting up the plugin, see the plugin's
+[README](https://pub.dev/packages/google_maps_flutter)
+page.
For help getting started with Flutter, view our online
[documentation](https://flutter.io/).
## Caveat
-The google_maps_flutter plugin provides an *unpublished preview* of the Flutter API for Google Maps:
+The google_maps_flutter plugin is in developer preview until [dynamic thread
+merging](https://github.com/flutter/flutter/projects/155) is finished.
* Dart APIs for controlling and interacting with a GoogleMap view from Flutter
code are still being consolidated and expanded. The intention is to grow
current coverage into a complete offering. Issues and pull requests aimed to
diff --git a/place_tracker/ios/Podfile b/place_tracker/ios/Podfile
index 0ee4a5378..1e8c3c90a 100644
--- a/place_tracker/ios/Podfile
+++ b/place_tracker/ios/Podfile
@@ -10,81 +10,32 @@ project 'Runner', {
'Release' => :release,
}
-def parse_KV_file(file, separator='=')
- file_abs_path = File.expand_path(file)
- if !File.exists? file_abs_path
- return [];
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
- generated_key_values = {}
- skip_line_start_symbols = ["#", "/"]
- File.foreach(file_abs_path) do |line|
- next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
- plugin = line.split(pattern=separator)
- if plugin.length == 2
- podname = plugin[0].strip()
- path = plugin[1].strip()
- podpath = File.expand_path("#{path}", file_abs_path)
- generated_key_values[podname] = podpath
- else
- puts "Invalid plugin specification: #{line}"
- end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
end
- generated_key_values
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
target 'Runner' do
use_frameworks!
use_modular_headers!
- # Flutter Pod
-
- copied_flutter_dir = File.join(__dir__, 'Flutter')
- copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
- copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
- unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
- # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
- # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
- # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
-
- generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
- unless File.exist?(generated_xcode_build_settings_path)
- raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
- generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
- cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
-
- unless File.exist?(copied_framework_path)
- FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
- end
- unless File.exist?(copied_podspec_path)
- FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
- end
- end
-
- # Keep pod path relative so it can be checked into Podfile.lock.
- pod 'Flutter', :path => 'Flutter'
-
- # Plugin Pods
-
- # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
- # referring to absolute paths on developers' machines.
- system('rm -rf .symlinks')
- system('mkdir -p .symlinks/plugins')
- plugin_pods = parse_KV_file('../.flutter-plugins')
- plugin_pods.each do |name, path|
- symlink = File.join('.symlinks', 'plugins', name)
- File.symlink(path, symlink)
- pod name, :path => File.join(symlink, 'ios')
- end
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
-# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
-install! 'cocoapods', :disable_input_output_paths => true
-
post_install do |installer|
installer.pods_project.targets.each do |target|
- target.build_configurations.each do |config|
- config.build_settings['ENABLE_BITCODE'] = 'NO'
- end
+ flutter_additional_ios_build_settings(target)
end
end
diff --git a/place_tracker/ios/Runner.xcodeproj/project.pbxproj b/place_tracker/ios/Runner.xcodeproj/project.pbxproj
index f080fc125..8adbb90c7 100644
--- a/place_tracker/ios/Runner.xcodeproj/project.pbxproj
+++ b/place_tracker/ios/Runner.xcodeproj/project.pbxproj
@@ -9,11 +9,8 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 60896A287CEE44DA12929F37 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AC813E96EEED8049372B007 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -26,8 +23,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -37,14 +32,16 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 23C21398A8F98F6248469BBA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 28467FEB0EFFEE00368A5568 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
+ 60CCDC9F87BA8463D14D1899 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 8AC813E96EEED8049372B007 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -57,20 +54,36 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
+ 60896A287CEE44DA12929F37 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 62C8D3D83EF1F84916A2F93E /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 8AC813E96EEED8049372B007 /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 8B9122CDB85400D9D46FA60E /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 60CCDC9F87BA8463D14D1899 /* Pods-Runner.debug.xcconfig */,
+ 23C21398A8F98F6248469BBA /* Pods-Runner.release.xcconfig */,
+ 28467FEB0EFFEE00368A5568 /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
- 3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -84,6 +97,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
+ 8B9122CDB85400D9D46FA60E /* Pods */,
+ 62C8D3D83EF1F84916A2F93E /* Frameworks */,
);
sourceTree = "";
};
@@ -125,12 +140,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ B606037D737D7C74CC55B500 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ CAF79D024134EA0C14FDEDC4 /* [CP] Embed Pods Frameworks */,
+ 2FBA60F1C5E1C14B292C896D /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -189,6 +207,24 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 2FBA60F1C5E1C14B292C896D /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
+ "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -201,7 +237,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
@@ -217,6 +253,46 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ B606037D737D7C74CC55B500 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ CAF79D024134EA0C14FDEDC4 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+ "${PODS_ROOT}/../Flutter/Flutter.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -253,7 +329,6 @@
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -330,7 +405,6 @@
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -386,7 +460,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
diff --git a/place_tracker/ios/Runner.xcworkspace/contents.xcworkspacedata b/place_tracker/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a16e..21a3cc14c 100644
--- a/place_tracker/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/place_tracker/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/place_tracker/lib/place.dart b/place_tracker/lib/place.dart
index 619a989c7..cb2812eff 100644
--- a/place_tracker/lib/place.dart
+++ b/place_tracker/lib/place.dart
@@ -1,13 +1,14 @@
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
-enum PlaceCategory {
- favorite,
- visited,
- wantToGo,
-}
-
class Place {
+ final String id;
+ final LatLng latLng;
+ final String name;
+ final PlaceCategory category;
+ final String description;
+ final int starRating;
+
const Place({
@required this.id,
@required this.latLng,
@@ -21,13 +22,6 @@ class Place {
assert(category != null),
assert(starRating != null && starRating >= 0 && starRating <= 5);
- final String id;
- final LatLng latLng;
- final String name;
- final PlaceCategory category;
- final String description;
- final int starRating;
-
double get latitude => latLng.latitude;
double get longitude => latLng.longitude;
@@ -50,3 +44,9 @@ class Place {
);
}
}
+
+enum PlaceCategory {
+ favorite,
+ visited,
+ wantToGo,
+}
diff --git a/place_tracker/lib/place_details.dart b/place_tracker/lib/place_details.dart
index f8287aa7f..06e7cbbb5 100644
--- a/place_tracker/lib/place_details.dart
+++ b/place_tracker/lib/place_details.dart
@@ -7,6 +7,9 @@ import 'place.dart';
import 'stub_data.dart';
class PlaceDetails extends StatefulWidget {
+ final Place place;
+ final ValueChanged onChanged;
+
const PlaceDetails({
@required this.place,
@required this.onChanged,
@@ -15,9 +18,6 @@ class PlaceDetails extends StatefulWidget {
assert(onChanged != null),
super(key: key);
- final Place place;
- final ValueChanged onChanged;
-
@override
PlaceDetailsState createState() => PlaceDetailsState();
}
@@ -26,10 +26,37 @@ class PlaceDetailsState extends State {
Place _place;
GoogleMapController _mapController;
final Set _markers = {};
-
final TextEditingController _nameController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('${_place.name}'),
+ backgroundColor: Colors.green[700],
+ actions: [
+ Padding(
+ padding: const EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0),
+ child: IconButton(
+ icon: const Icon(Icons.save, size: 30.0),
+ onPressed: () {
+ widget.onChanged(_place);
+ Navigator.pop(context);
+ },
+ ),
+ ),
+ ],
+ ),
+ body: GestureDetector(
+ onTap: () {
+ FocusScope.of(context).requestFocus(FocusNode());
+ },
+ child: _detailsBody(),
+ ),
+ );
+ }
+
@override
void initState() {
_place = widget.place;
@@ -38,20 +65,10 @@ class PlaceDetailsState extends State {
return super.initState();
}
- void _onMapCreated(GoogleMapController controller) {
- _mapController = controller;
- setState(() {
- _markers.add(Marker(
- markerId: MarkerId(_place.latLng.toString()),
- position: _place.latLng,
- ));
- });
- }
-
Widget _detailsBody() {
return ListView(
padding: const EdgeInsets.fromLTRB(24.0, 12.0, 24.0, 12.0),
- children: [
+ children: [
_NameTextField(
controller: _nameController,
onChanged: (value) {
@@ -87,68 +104,21 @@ class PlaceDetailsState extends State {
);
}
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text('${_place.name}'),
- backgroundColor: Colors.green[700],
- actions: [
- Padding(
- padding: const EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0),
- child: IconButton(
- icon: const Icon(Icons.save, size: 30.0),
- onPressed: () {
- widget.onChanged(_place);
- Navigator.pop(context);
- },
- ),
- ),
- ],
- ),
- body: GestureDetector(
- onTap: () {
- FocusScope.of(context).requestFocus(FocusNode());
- },
- child: _detailsBody(),
- ),
- );
+ void _onMapCreated(GoogleMapController controller) {
+ _mapController = controller;
+ setState(() {
+ _markers.add(Marker(
+ markerId: MarkerId(_place.latLng.toString()),
+ position: _place.latLng,
+ ));
+ });
}
}
-class _NameTextField extends StatelessWidget {
- const _NameTextField({
- @required this.controller,
- @required this.onChanged,
- Key key,
- }) : assert(controller != null),
- assert(onChanged != null),
- super(key: key);
-
+class _DescriptionTextField extends StatelessWidget {
final TextEditingController controller;
- final ValueChanged onChanged;
- @override
- Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 16.0),
- child: TextField(
- decoration: const InputDecoration(
- labelText: 'Name',
- labelStyle: TextStyle(fontSize: 18.0),
- ),
- style: const TextStyle(fontSize: 20.0, color: Colors.black87),
- autocorrect: true,
- controller: controller,
- onChanged: (value) {
- onChanged(value);
- },
- ),
- );
- }
-}
-
-class _DescriptionTextField extends StatelessWidget {
+ final ValueChanged onChanged;
const _DescriptionTextField({
@required this.controller,
@required this.onChanged,
@@ -157,9 +127,6 @@ class _DescriptionTextField extends StatelessWidget {
assert(onChanged != null),
super(key: key);
- final TextEditingController controller;
- final ValueChanged onChanged;
-
@override
Widget build(BuildContext context) {
return Padding(
@@ -181,38 +148,12 @@ class _DescriptionTextField extends StatelessWidget {
}
}
-class _StarBar extends StatelessWidget {
- const _StarBar({
- @required this.rating,
- @required this.onChanged,
- Key key,
- }) : assert(rating != null && rating >= 0 && rating <= maxStars),
- assert(onChanged != null),
- super(key: key);
-
- static const int maxStars = 5;
- final int rating;
- final ValueChanged onChanged;
-
- @override
- Widget build(BuildContext context) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: List.generate(maxStars, (index) {
- return IconButton(
- icon: const Icon(Icons.star),
- iconSize: 40.0,
- color: rating > index ? Colors.amber : Colors.grey[400],
- onPressed: () {
- onChanged(index + 1);
- },
- );
- }).toList(),
- );
- }
-}
-
class _Map extends StatelessWidget {
+ final LatLng center;
+
+ final GoogleMapController mapController;
+ final ArgumentCallback onMapCreated;
+ final Set markers;
const _Map({
@required this.center,
@required this.mapController,
@@ -223,24 +164,19 @@ class _Map extends StatelessWidget {
assert(onMapCreated != null),
super(key: key);
- final LatLng center;
- final GoogleMapController mapController;
- final ArgumentCallback onMapCreated;
- final Set markers;
-
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 16.0),
- elevation: 4.0,
+ elevation: 4,
child: SizedBox(
- width: 340.0,
- height: 240.0,
+ width: 340,
+ height: 240,
child: GoogleMap(
onMapCreated: onMapCreated,
initialCameraPosition: CameraPosition(
target: center,
- zoom: 16.0,
+ zoom: 16,
),
markers: markers,
zoomGesturesEnabled: false,
@@ -253,35 +189,95 @@ class _Map extends StatelessWidget {
}
}
+class _NameTextField extends StatelessWidget {
+ final TextEditingController controller;
+
+ final ValueChanged onChanged;
+ const _NameTextField({
+ @required this.controller,
+ @required this.onChanged,
+ Key key,
+ }) : assert(controller != null),
+ assert(onChanged != null),
+ super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.fromLTRB(0, 0, 0, 16),
+ child: TextField(
+ decoration: const InputDecoration(
+ labelText: 'Name',
+ labelStyle: TextStyle(fontSize: 18),
+ ),
+ style: const TextStyle(fontSize: 20, color: Colors.black87),
+ autocorrect: true,
+ controller: controller,
+ onChanged: (value) {
+ onChanged(value);
+ },
+ ),
+ );
+ }
+}
+
class _Reviews extends StatelessWidget {
const _Reviews({
Key key,
}) : super(key: key);
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ const Padding(
+ padding: EdgeInsets.fromLTRB(0, 12, 0, 8),
+ child: Align(
+ alignment: Alignment.topLeft,
+ child: Text(
+ 'Reviews',
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ decoration: TextDecoration.underline,
+ color: Colors.black87,
+ ),
+ ),
+ ),
+ ),
+ Column(
+ children: StubData.reviewStrings
+ .map((reviewText) => _buildSingleReview(reviewText))
+ .toList(),
+ ),
+ ],
+ );
+ }
+
Widget _buildSingleReview(String reviewText) {
return Column(
- children: [
+ children: [
Padding(
- padding: const EdgeInsets.symmetric(vertical: 10.0),
+ padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
- children: [
+ children: [
Container(
- width: 80.0,
- height: 80.0,
+ width: 80,
+ height: 80,
decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(40.0),
+ borderRadius: BorderRadius.circular(40),
border: Border.all(
- width: 3.0,
+ width: 3,
color: Colors.grey,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
- children: const [
+ children: const [
Text(
'5',
style: TextStyle(
- fontSize: 24.0,
+ fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
@@ -289,16 +285,16 @@ class _Reviews extends StatelessWidget {
Icon(
Icons.star,
color: Colors.amber,
- size: 36.0,
+ size: 36,
),
],
),
),
- const SizedBox(width: 16.0),
+ const SizedBox(width: 16),
Expanded(
child: Text(
reviewText,
- style: const TextStyle(fontSize: 20.0, color: Colors.black87),
+ style: const TextStyle(fontSize: 20, color: Colors.black87),
maxLines: null,
),
),
@@ -306,38 +302,41 @@ class _Reviews extends StatelessWidget {
),
),
Divider(
- height: 8.0,
+ height: 8,
color: Colors.grey[700],
),
],
);
}
+}
+
+class _StarBar extends StatelessWidget {
+ static const int maxStars = 5;
+
+ final int rating;
+ final ValueChanged onChanged;
+ const _StarBar({
+ @required this.rating,
+ @required this.onChanged,
+ Key key,
+ }) : assert(rating != null && rating >= 0 && rating <= maxStars),
+ assert(onChanged != null),
+ super(key: key);
@override
Widget build(BuildContext context) {
- return Column(
- children: [
- const Padding(
- padding: EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 8.0),
- child: Align(
- alignment: Alignment.topLeft,
- child: Text(
- 'Reviews',
- style: TextStyle(
- fontSize: 24.0,
- fontWeight: FontWeight.bold,
- decoration: TextDecoration.underline,
- color: Colors.black87,
- ),
- ),
- ),
- ),
- Column(
- children: StubData.reviewStrings
- .map((reviewText) => _buildSingleReview(reviewText))
- .toList(),
- ),
- ],
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: List.generate(maxStars, (index) {
+ return IconButton(
+ icon: const Icon(Icons.star),
+ iconSize: 40,
+ color: rating > index ? Colors.amber : Colors.grey[400],
+ onPressed: () {
+ onChanged(index + 1);
+ },
+ );
+ }).toList(),
);
}
}
diff --git a/place_tracker/lib/place_list.dart b/place_tracker/lib/place_list.dart
index dd76332ee..0fe138821 100644
--- a/place_tracker/lib/place_list.dart
+++ b/place_tracker/lib/place_list.dart
@@ -15,27 +15,11 @@ class PlaceList extends StatefulWidget {
class PlaceListState extends State {
final ScrollController _scrollController = ScrollController();
- void _onCategoryChanged(PlaceCategory newCategory) {
- _scrollController.jumpTo(0.0);
- Provider.of(context, listen: false)
- .setSelectedCategory(newCategory);
- }
-
- void _onPlaceChanged(Place value) {
- // Replace the place with the modified version.
- final newPlaces =
- List.from(Provider.of(context, listen: false).places);
- final index = newPlaces.indexWhere((place) => place.id == value.id);
- newPlaces[index] = value;
-
- Provider.of(context, listen: false).setPlaces(newPlaces);
- }
-
@override
Widget build(BuildContext context) {
var state = Provider.of(context);
return Column(
- children: [
+ children: [
_ListCategoryButtonBar(
selectedCategory: state.selectedCategory,
onCategoryChanged: (value) => _onCategoryChanged(value),
@@ -57,9 +41,119 @@ class PlaceListState extends State {
],
);
}
+
+ void _onCategoryChanged(PlaceCategory newCategory) {
+ _scrollController.jumpTo(0.0);
+ Provider.of(context, listen: false)
+ .setSelectedCategory(newCategory);
+ }
+
+ void _onPlaceChanged(Place value) {
+ // Replace the place with the modified version.
+ final newPlaces =
+ List.from(Provider.of(context, listen: false).places);
+ final index = newPlaces.indexWhere((place) => place.id == value.id);
+ newPlaces[index] = value;
+
+ Provider.of(context, listen: false).setPlaces(newPlaces);
+ }
+}
+
+class _CategoryButton extends StatelessWidget {
+ final PlaceCategory category;
+
+ final bool selected;
+ final ValueChanged onCategoryChanged;
+ const _CategoryButton({
+ Key key,
+ @required this.category,
+ @required this.selected,
+ @required this.onCategoryChanged,
+ }) : assert(category != null),
+ assert(selected != null),
+ super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ String _buttonText;
+ switch (category) {
+ case PlaceCategory.favorite:
+ _buttonText = 'Favorites';
+ break;
+ case PlaceCategory.visited:
+ _buttonText = 'Visited';
+ break;
+ case PlaceCategory.wantToGo:
+ _buttonText = 'Want To Go';
+ }
+
+ return Container(
+ margin: EdgeInsets.symmetric(vertical: 12.0),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: selected ? Colors.blue : Colors.transparent,
+ ),
+ ),
+ ),
+ child: ButtonTheme(
+ height: 50.0,
+ child: FlatButton(
+ child: Text(
+ _buttonText,
+ style: TextStyle(
+ fontSize: selected ? 20.0 : 18.0,
+ color: selected ? Colors.blue : Colors.black87,
+ ),
+ ),
+ onPressed: () => onCategoryChanged(category),
+ ),
+ ),
+ );
+ }
+}
+
+class _ListCategoryButtonBar extends StatelessWidget {
+ final PlaceCategory selectedCategory;
+
+ final ValueChanged onCategoryChanged;
+ const _ListCategoryButtonBar({
+ Key key,
+ @required this.selectedCategory,
+ @required this.onCategoryChanged,
+ }) : assert(selectedCategory != null),
+ assert(onCategoryChanged != null),
+ super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ _CategoryButton(
+ category: PlaceCategory.favorite,
+ selected: selectedCategory == PlaceCategory.favorite,
+ onCategoryChanged: onCategoryChanged,
+ ),
+ _CategoryButton(
+ category: PlaceCategory.visited,
+ selected: selectedCategory == PlaceCategory.visited,
+ onCategoryChanged: onCategoryChanged,
+ ),
+ _CategoryButton(
+ category: PlaceCategory.wantToGo,
+ selected: selectedCategory == PlaceCategory.wantToGo,
+ onCategoryChanged: onCategoryChanged,
+ ),
+ ],
+ );
+ }
}
class _PlaceListTile extends StatelessWidget {
+ final Place place;
+
+ final ValueChanged onPlaceChanged;
const _PlaceListTile({
Key key,
@required this.place,
@@ -68,9 +162,6 @@ class _PlaceListTile extends StatelessWidget {
assert(onPlaceChanged != null),
super(key: key);
- final Place place;
- final ValueChanged onPlaceChanged;
-
@override
Widget build(BuildContext context) {
return InkWell(
@@ -87,7 +178,7 @@ class _PlaceListTile extends StatelessWidget {
padding: EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
- children: [
+ children: [
Text(
place.name,
textAlign: TextAlign.left,
@@ -125,94 +216,3 @@ class _PlaceListTile extends StatelessWidget {
);
}
}
-
-class _ListCategoryButtonBar extends StatelessWidget {
- const _ListCategoryButtonBar({
- Key key,
- @required this.selectedCategory,
- @required this.onCategoryChanged,
- }) : assert(selectedCategory != null),
- assert(onCategoryChanged != null),
- super(key: key);
-
- final PlaceCategory selectedCategory;
- final ValueChanged onCategoryChanged;
-
- @override
- Widget build(BuildContext context) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- _CategoryButton(
- category: PlaceCategory.favorite,
- selected: selectedCategory == PlaceCategory.favorite,
- onCategoryChanged: onCategoryChanged,
- ),
- _CategoryButton(
- category: PlaceCategory.visited,
- selected: selectedCategory == PlaceCategory.visited,
- onCategoryChanged: onCategoryChanged,
- ),
- _CategoryButton(
- category: PlaceCategory.wantToGo,
- selected: selectedCategory == PlaceCategory.wantToGo,
- onCategoryChanged: onCategoryChanged,
- ),
- ],
- );
- }
-}
-
-class _CategoryButton extends StatelessWidget {
- const _CategoryButton({
- Key key,
- @required this.category,
- @required this.selected,
- @required this.onCategoryChanged,
- }) : assert(category != null),
- assert(selected != null),
- super(key: key);
-
- final PlaceCategory category;
- final bool selected;
- final ValueChanged onCategoryChanged;
-
- @override
- Widget build(BuildContext context) {
- String _buttonText;
- switch (category) {
- case PlaceCategory.favorite:
- _buttonText = 'Favorites';
- break;
- case PlaceCategory.visited:
- _buttonText = 'Visited';
- break;
- case PlaceCategory.wantToGo:
- _buttonText = 'Want To Go';
- }
-
- return Container(
- margin: EdgeInsets.symmetric(vertical: 12.0),
- decoration: BoxDecoration(
- border: Border(
- bottom: BorderSide(
- color: selected ? Colors.blue : Colors.transparent,
- ),
- ),
- ),
- child: ButtonTheme(
- height: 50.0,
- child: FlatButton(
- child: Text(
- _buttonText,
- style: TextStyle(
- fontSize: selected ? 20.0 : 18.0,
- color: selected ? Colors.blue : Colors.black87,
- ),
- ),
- onPressed: () => onCategoryChanged(category),
- ),
- ),
- );
- }
-}
diff --git a/place_tracker/lib/place_map.dart b/place_tracker/lib/place_map.dart
index c266f8386..1b2c184f4 100644
--- a/place_tracker/lib/place_map.dart
+++ b/place_tracker/lib/place_map.dart
@@ -11,41 +11,55 @@ import 'place.dart';
import 'place_details.dart';
import 'place_tracker_app.dart';
+class MapConfiguration {
+ final List places;
+
+ final PlaceCategory selectedCategory;
+ const MapConfiguration({
+ @required this.places,
+ @required this.selectedCategory,
+ }) : assert(places != null),
+ assert(selectedCategory != null);
+
+ @override
+ int get hashCode => places.hashCode ^ selectedCategory.hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) {
+ return true;
+ }
+
+ if (other.runtimeType != runtimeType) {
+ return false;
+ }
+
+ return other is MapConfiguration &&
+ other.places == places &&
+ other.selectedCategory == selectedCategory;
+ }
+
+ static MapConfiguration of(AppState appState) {
+ return MapConfiguration(
+ places: appState.places,
+ selectedCategory: appState.selectedCategory,
+ );
+ }
+}
+
class PlaceMap extends StatefulWidget {
+ final LatLng center;
+
const PlaceMap({
Key key,
this.center,
}) : super(key: key);
- final LatLng center;
-
@override
PlaceMapState createState() => PlaceMapState();
}
class PlaceMapState extends State {
- static Future _getPlaceMarkerIcon(
- BuildContext context, PlaceCategory category) async {
- switch (category) {
- case PlaceCategory.favorite:
- return BitmapDescriptor.fromAssetImage(
- createLocalImageConfiguration(context), 'assets/heart.png');
- break;
- case PlaceCategory.visited:
- return BitmapDescriptor.fromAssetImage(
- createLocalImageConfiguration(context), 'assets/visited.png');
- break;
- case PlaceCategory.wantToGo:
- default:
- return BitmapDescriptor.defaultMarker;
- }
- }
-
- static List _getPlacesForCategory(
- PlaceCategory category, List places) {
- return places.where((place) => place.category == category).toList();
- }
-
Completer mapController = Completer();
MapType _currentMapType = MapType.normal;
@@ -60,6 +74,50 @@ class PlaceMapState extends State {
MapConfiguration _configuration;
+ @override
+ Widget build(BuildContext context) {
+ _maybeUpdateMapConfiguration();
+ var state = Provider.of(context);
+
+ return Builder(builder: (context) {
+ // We need this additional builder here so that we can pass its context to
+ // _AddPlaceButtonBar's onSavePressed callback. This callback shows a
+ // SnackBar and to do this, we need a build context that has Scaffold as
+ // an ancestor.
+ return Center(
+ child: Stack(
+ children: [
+ GoogleMap(
+ onMapCreated: onMapCreated,
+ initialCameraPosition: CameraPosition(
+ target: widget.center,
+ zoom: 11.0,
+ ),
+ mapType: _currentMapType,
+ markers: _markers,
+ onCameraMove: (position) => _lastMapPosition = position.target,
+ ),
+ _CategoryButtonBar(
+ selectedPlaceCategory: state.selectedCategory,
+ visible: _pendingMarker == null,
+ onChanged: _switchSelectedCategory,
+ ),
+ _AddPlaceButtonBar(
+ visible: _pendingMarker != null,
+ onSavePressed: () => _confirmAddPlace(context),
+ onCancelPressed: _cancelAddPlace,
+ ),
+ _MapFabs(
+ visible: _pendingMarker == null,
+ onAddPlacePressed: _onAddPlacePressed,
+ onToggleMapTypePressed: _onToggleMapTypePressed,
+ ),
+ ],
+ ),
+ );
+ });
+ }
+
Future onMapCreated(GoogleMapController controller) async {
mapController.complete(controller);
_lastMapPosition = widget.center;
@@ -83,152 +141,13 @@ class PlaceMapState extends State {
);
}
- Future _createPlaceMarker(BuildContext context, Place place) async {
- final marker = Marker(
- markerId: MarkerId(place.latLng.toString()),
- position: place.latLng,
- infoWindow: InfoWindow(
- title: place.name,
- snippet: '${place.starRating} Star Rating',
- onTap: () => _pushPlaceDetailsScreen(place),
- ),
- icon: await _getPlaceMarkerIcon(context, place.category),
- visible: place.category ==
- Provider.of(context, listen: false).selectedCategory,
- );
- _markedPlaces[marker] = place;
- return marker;
- }
-
- void _pushPlaceDetailsScreen(Place place) {
- assert(place != null);
-
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) {
- return PlaceDetails(
- place: place,
- onChanged: (value) => _onPlaceChanged(value),
- );
- }),
- );
- }
-
- void _onPlaceChanged(Place value) {
- // Replace the place with the modified version.
- final newPlaces =
- List.from(Provider.of(context, listen: false).places);
- final index = newPlaces.indexWhere((place) => place.id == value.id);
- newPlaces[index] = value;
-
- _updateExistingPlaceMarker(place: value);
-
- // Manually update our map configuration here since our map is already
- // updated with the new marker. Otherwise, the map would be reconfigured
- // in the main build method due to a modified AppState.
- _configuration = MapConfiguration(
- places: newPlaces,
- selectedCategory:
- Provider.of(context, listen: false).selectedCategory,
- );
-
- Provider.of(context, listen: false).setPlaces(newPlaces);
- }
-
- void _updateExistingPlaceMarker({@required Place place}) {
- var marker = _markedPlaces.keys
- .singleWhere((value) => _markedPlaces[value].id == place.id);
-
- setState(() {
- final updatedMarker = marker.copyWith(
- infoWindowParam: InfoWindow(
- title: place.name,
- snippet:
- place.starRating != 0 ? '${place.starRating} Star Rating' : null,
- ),
- );
- _updateMarker(marker: marker, updatedMarker: updatedMarker, place: place);
- });
- }
-
- void _updateMarker({
- @required Marker marker,
- @required Marker updatedMarker,
- @required Place place,
- }) {
- _markers.remove(marker);
- _markedPlaces.remove(marker);
-
- _markers.add(updatedMarker);
- _markedPlaces[updatedMarker] = place;
- }
-
- Future _switchSelectedCategory(PlaceCategory category) async {
- Provider.of(context, listen: false).setSelectedCategory(category);
- await _showPlacesForSelectedCategory(category);
- }
-
- Future _showPlacesForSelectedCategory(PlaceCategory category) async {
- setState(() {
- for (var marker in List.of(_markedPlaces.keys)) {
- final place = _markedPlaces[marker];
- final updatedMarker = marker.copyWith(
- visibleParam: place.category == category,
- );
-
- _updateMarker(
- marker: marker,
- updatedMarker: updatedMarker,
- place: place,
- );
- }
- });
-
- await _zoomToFitPlaces(_getPlacesForCategory(
- category,
- _markedPlaces.values.toList(),
- ));
- }
-
- Future _zoomToFitPlaces(List places) async {
- var controller = await mapController.future;
-
- // Default min/max values to latitude and longitude of center.
- var minLat = widget.center.latitude;
- var maxLat = widget.center.latitude;
- var minLong = widget.center.longitude;
- var maxLong = widget.center.longitude;
-
- for (var place in places) {
- minLat = min(minLat, place.latitude);
- maxLat = max(maxLat, place.latitude);
- minLong = min(minLong, place.longitude);
- maxLong = max(maxLong, place.longitude);
+ void _cancelAddPlace() {
+ if (_pendingMarker != null) {
+ setState(() {
+ _markers.remove(_pendingMarker);
+ _pendingMarker = null;
+ });
}
-
- await controller.animateCamera(
- CameraUpdate.newLatLngBounds(
- LatLngBounds(
- southwest: LatLng(minLat, minLong),
- northeast: LatLng(maxLat, maxLong),
- ),
- 48.0,
- ),
- );
- }
-
- Future _onAddPlacePressed() async {
- setState(() {
- final newMarker = Marker(
- markerId: MarkerId(_lastMapPosition.toString()),
- position: _lastMapPosition,
- infoWindow: InfoWindow(title: 'New Place'),
- draggable: true,
- icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
- );
- _markers.add(newMarker);
- _pendingMarker = newMarker;
- });
}
Future _confirmAddPlace(BuildContext context) async {
@@ -298,22 +217,21 @@ class PlaceMapState extends State {
}
}
- void _cancelAddPlace() {
- if (_pendingMarker != null) {
- setState(() {
- _markers.remove(_pendingMarker);
- _pendingMarker = null;
- });
- }
- }
-
- void _onToggleMapTypePressed() {
- final nextType =
- MapType.values[(_currentMapType.index + 1) % MapType.values.length];
-
- setState(() {
- _currentMapType = nextType;
- });
+ Future _createPlaceMarker(BuildContext context, Place place) async {
+ final marker = Marker(
+ markerId: MarkerId(place.latLng.toString()),
+ position: place.latLng,
+ infoWindow: InfoWindow(
+ title: place.name,
+ snippet: '${place.starRating} Star Rating',
+ onTap: () => _pushPlaceDetailsScreen(place),
+ ),
+ icon: await _getPlaceMarkerIcon(context, place.category),
+ visible: place.category ==
+ Provider.of(context, listen: false).selectedCategory,
+ );
+ _markedPlaces[marker] = place;
+ return marker;
}
Future _maybeUpdateMapConfiguration() async {
@@ -351,52 +269,222 @@ class PlaceMapState extends State {
}
}
+ Future _onAddPlacePressed() async {
+ setState(() {
+ final newMarker = Marker(
+ markerId: MarkerId(_lastMapPosition.toString()),
+ position: _lastMapPosition,
+ infoWindow: InfoWindow(title: 'New Place'),
+ draggable: true,
+ icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
+ );
+ _markers.add(newMarker);
+ _pendingMarker = newMarker;
+ });
+ }
+
+ void _onPlaceChanged(Place value) {
+ // Replace the place with the modified version.
+ final newPlaces =
+ List.from(Provider.of(context, listen: false).places);
+ final index = newPlaces.indexWhere((place) => place.id == value.id);
+ newPlaces[index] = value;
+
+ _updateExistingPlaceMarker(place: value);
+
+ // Manually update our map configuration here since our map is already
+ // updated with the new marker. Otherwise, the map would be reconfigured
+ // in the main build method due to a modified AppState.
+ _configuration = MapConfiguration(
+ places: newPlaces,
+ selectedCategory:
+ Provider.of(context, listen: false).selectedCategory,
+ );
+
+ Provider.of(context, listen: false).setPlaces(newPlaces);
+ }
+
+ void _onToggleMapTypePressed() {
+ final nextType =
+ MapType.values[(_currentMapType.index + 1) % MapType.values.length];
+
+ setState(() {
+ _currentMapType = nextType;
+ });
+ }
+
+ void _pushPlaceDetailsScreen(Place place) {
+ assert(place != null);
+
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) {
+ return PlaceDetails(
+ place: place,
+ onChanged: (value) => _onPlaceChanged(value),
+ );
+ }),
+ );
+ }
+
+ Future _showPlacesForSelectedCategory(PlaceCategory category) async {
+ setState(() {
+ for (var marker in List.of(_markedPlaces.keys)) {
+ final place = _markedPlaces[marker];
+ final updatedMarker = marker.copyWith(
+ visibleParam: place.category == category,
+ );
+
+ _updateMarker(
+ marker: marker,
+ updatedMarker: updatedMarker,
+ place: place,
+ );
+ }
+ });
+
+ await _zoomToFitPlaces(_getPlacesForCategory(
+ category,
+ _markedPlaces.values.toList(),
+ ));
+ }
+
+ Future _switchSelectedCategory(PlaceCategory category) async {
+ Provider.of(context, listen: false).setSelectedCategory(category);
+ await _showPlacesForSelectedCategory(category);
+ }
+
+ void _updateExistingPlaceMarker({@required Place place}) {
+ var marker = _markedPlaces.keys
+ .singleWhere((value) => _markedPlaces[value].id == place.id);
+
+ setState(() {
+ final updatedMarker = marker.copyWith(
+ infoWindowParam: InfoWindow(
+ title: place.name,
+ snippet:
+ place.starRating != 0 ? '${place.starRating} Star Rating' : null,
+ ),
+ );
+ _updateMarker(marker: marker, updatedMarker: updatedMarker, place: place);
+ });
+ }
+
+ void _updateMarker({
+ @required Marker marker,
+ @required Marker updatedMarker,
+ @required Place place,
+ }) {
+ _markers.remove(marker);
+ _markedPlaces.remove(marker);
+
+ _markers.add(updatedMarker);
+ _markedPlaces[updatedMarker] = place;
+ }
+
+ Future _zoomToFitPlaces(List places) async {
+ var controller = await mapController.future;
+
+ // Default min/max values to latitude and longitude of center.
+ var minLat = widget.center.latitude;
+ var maxLat = widget.center.latitude;
+ var minLong = widget.center.longitude;
+ var maxLong = widget.center.longitude;
+
+ for (var place in places) {
+ minLat = min(minLat, place.latitude);
+ maxLat = max(maxLat, place.latitude);
+ minLong = min(minLong, place.longitude);
+ maxLong = max(maxLong, place.longitude);
+ }
+
+ await controller.animateCamera(
+ CameraUpdate.newLatLngBounds(
+ LatLngBounds(
+ southwest: LatLng(minLat, minLong),
+ northeast: LatLng(maxLat, maxLong),
+ ),
+ 48.0,
+ ),
+ );
+ }
+
+ static Future _getPlaceMarkerIcon(
+ BuildContext context, PlaceCategory category) async {
+ switch (category) {
+ case PlaceCategory.favorite:
+ return BitmapDescriptor.fromAssetImage(
+ createLocalImageConfiguration(context), 'assets/heart.png');
+ break;
+ case PlaceCategory.visited:
+ return BitmapDescriptor.fromAssetImage(
+ createLocalImageConfiguration(context), 'assets/visited.png');
+ break;
+ case PlaceCategory.wantToGo:
+ default:
+ return BitmapDescriptor.defaultMarker;
+ }
+ }
+
+ static List _getPlacesForCategory(
+ PlaceCategory category, List places) {
+ return places.where((place) => place.category == category).toList();
+ }
+}
+
+class _AddPlaceButtonBar extends StatelessWidget {
+ final bool visible;
+
+ final VoidCallback onSavePressed;
+ final VoidCallback onCancelPressed;
+ const _AddPlaceButtonBar({
+ Key key,
+ @required this.visible,
+ @required this.onSavePressed,
+ @required this.onCancelPressed,
+ }) : assert(visible != null),
+ assert(onSavePressed != null),
+ assert(onCancelPressed != null),
+ super(key: key);
+
@override
Widget build(BuildContext context) {
- _maybeUpdateMapConfiguration();
- var state = Provider.of(context);
-
- return Builder(builder: (context) {
- // We need this additional builder here so that we can pass its context to
- // _AddPlaceButtonBar's onSavePressed callback. This callback shows a
- // SnackBar and to do this, we need a build context that has Scaffold as
- // an ancestor.
- return Center(
- child: Stack(
- children: [
- GoogleMap(
- onMapCreated: onMapCreated,
- initialCameraPosition: CameraPosition(
- target: widget.center,
- zoom: 11.0,
+ return Visibility(
+ visible: visible,
+ child: Container(
+ padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 14.0),
+ alignment: Alignment.bottomCenter,
+ child: ButtonBar(
+ alignment: MainAxisAlignment.center,
+ children: [
+ RaisedButton(
+ color: Colors.blue,
+ child: const Text(
+ 'Save',
+ style: TextStyle(color: Colors.white, fontSize: 16.0),
),
- mapType: _currentMapType,
- markers: _markers,
- onCameraMove: (position) => _lastMapPosition = position.target,
- ),
- _CategoryButtonBar(
- selectedPlaceCategory: state.selectedCategory,
- visible: _pendingMarker == null,
- onChanged: _switchSelectedCategory,
- ),
- _AddPlaceButtonBar(
- visible: _pendingMarker != null,
- onSavePressed: () => _confirmAddPlace(context),
- onCancelPressed: _cancelAddPlace,
+ onPressed: onSavePressed,
),
- _MapFabs(
- visible: _pendingMarker == null,
- onAddPlacePressed: _onAddPlacePressed,
- onToggleMapTypePressed: _onToggleMapTypePressed,
+ RaisedButton(
+ color: Colors.red,
+ child: const Text(
+ 'Cancel',
+ style: TextStyle(color: Colors.white, fontSize: 16.0),
+ ),
+ onPressed: onCancelPressed,
),
],
),
- );
- });
+ ),
+ );
}
}
class _CategoryButtonBar extends StatelessWidget {
+ final PlaceCategory selectedPlaceCategory;
+ final bool visible;
+ final ValueChanged onChanged;
+
const _CategoryButtonBar({
Key key,
@required this.selectedPlaceCategory,
@@ -407,10 +495,6 @@ class _CategoryButtonBar extends StatelessWidget {
assert(onChanged != null),
super(key: key);
- final PlaceCategory selectedPlaceCategory;
- final bool visible;
- final ValueChanged onChanged;
-
@override
Widget build(BuildContext context) {
return Visibility(
@@ -420,7 +504,7 @@ class _CategoryButtonBar extends StatelessWidget {
alignment: Alignment.bottomCenter,
child: ButtonBar(
alignment: MainAxisAlignment.center,
- children: [
+ children: [
RaisedButton(
color: selectedPlaceCategory == PlaceCategory.favorite
? Colors.green[700]
@@ -458,55 +542,11 @@ class _CategoryButtonBar extends StatelessWidget {
}
}
-class _AddPlaceButtonBar extends StatelessWidget {
- const _AddPlaceButtonBar({
- Key key,
- @required this.visible,
- @required this.onSavePressed,
- @required this.onCancelPressed,
- }) : assert(visible != null),
- assert(onSavePressed != null),
- assert(onCancelPressed != null),
- super(key: key);
-
+class _MapFabs extends StatelessWidget {
final bool visible;
- final VoidCallback onSavePressed;
- final VoidCallback onCancelPressed;
-
- @override
- Widget build(BuildContext context) {
- return Visibility(
- visible: visible,
- child: Container(
- padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 14.0),
- alignment: Alignment.bottomCenter,
- child: ButtonBar(
- alignment: MainAxisAlignment.center,
- children: [
- RaisedButton(
- color: Colors.blue,
- child: const Text(
- 'Save',
- style: TextStyle(color: Colors.white, fontSize: 16.0),
- ),
- onPressed: onSavePressed,
- ),
- RaisedButton(
- color: Colors.red,
- child: const Text(
- 'Cancel',
- style: TextStyle(color: Colors.white, fontSize: 16.0),
- ),
- onPressed: onCancelPressed,
- ),
- ],
- ),
- ),
- );
- }
-}
+ final VoidCallback onAddPlacePressed;
+ final VoidCallback onToggleMapTypePressed;
-class _MapFabs extends StatelessWidget {
const _MapFabs({
Key key,
@required this.visible,
@@ -517,10 +557,6 @@ class _MapFabs extends StatelessWidget {
assert(onToggleMapTypePressed != null),
super(key: key);
- final bool visible;
- final VoidCallback onAddPlacePressed;
- final VoidCallback onToggleMapTypePressed;
-
@override
Widget build(BuildContext context) {
return Container(
@@ -529,7 +565,7 @@ class _MapFabs extends StatelessWidget {
child: Visibility(
visible: visible,
child: Column(
- children: [
+ children: [
FloatingActionButton(
heroTag: 'add_place_button',
onPressed: onAddPlacePressed,
@@ -552,39 +588,3 @@ class _MapFabs extends StatelessWidget {
);
}
}
-
-class MapConfiguration {
- const MapConfiguration({
- @required this.places,
- @required this.selectedCategory,
- }) : assert(places != null),
- assert(selectedCategory != null);
-
- final List places;
- final PlaceCategory selectedCategory;
-
- @override
- int get hashCode => places.hashCode ^ selectedCategory.hashCode;
-
- @override
- bool operator ==(Object other) {
- if (identical(this, other)) {
- return true;
- }
-
- if (other.runtimeType != runtimeType) {
- return false;
- }
-
- return other is MapConfiguration &&
- other.places == places &&
- other.selectedCategory == selectedCategory;
- }
-
- static MapConfiguration of(AppState appState) {
- return MapConfiguration(
- places: appState.places,
- selectedCategory: appState.selectedCategory,
- );
- }
-}
diff --git a/place_tracker/lib/place_tracker_app.dart b/place_tracker/lib/place_tracker_app.dart
index d5b38c660..6fdcb4114 100644
--- a/place_tracker/lib/place_tracker_app.dart
+++ b/place_tracker/lib/place_tracker_app.dart
@@ -31,7 +31,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
appBar: AppBar(
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
- children: const [
+ children: const [
Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0),
child: Icon(Icons.pin_drop, size: 24.0),
@@ -40,7 +40,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
],
),
backgroundColor: Colors.green[700],
- actions: [
+ actions: [
Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0),
child: IconButton(
@@ -63,7 +63,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
),
body: IndexedStack(
index: state.viewType == PlaceTrackerViewType.map ? 0 : 1,
- children: [
+ children: [
PlaceMap(center: const LatLng(45.521563, -122.677433)),
PlaceList()
],
@@ -102,7 +102,6 @@ class AppState extends ChangeNotifier {
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
- if (other.runtimeType != runtimeType) return false;
return other is AppState &&
other.places == places &&
other.selectedCategory == selectedCategory &&
diff --git a/place_tracker/lib/stub_data.dart b/place_tracker/lib/stub_data.dart
index 35d9b2969..984828b5c 100644
--- a/place_tracker/lib/stub_data.dart
+++ b/place_tracker/lib/stub_data.dart
@@ -141,7 +141,7 @@ class StubData {
),
];
- static const List reviewStrings = [
+ static const reviewStrings = [
'My favorite place in Portland. The employees are wonderful and so is the food. I go here at least once a month!',
'Staff was very friendly. Great atmosphere and good music. Would reccommend.',
'Best. Place. In. Town. Period.'
diff --git a/place_tracker/pubspec.lock b/place_tracker/pubspec.lock
index ff4695b52..f370c2c13 100644
--- a/place_tracker/pubspec.lock
+++ b/place_tracker/pubspec.lock
@@ -56,7 +56,7 @@ packages:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.4"
+ version: "2.1.5"
cupertino_icons:
dependency: "direct main"
description:
@@ -82,7 +82,7 @@ packages:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.7"
+ version: "1.0.8"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -94,14 +94,14 @@ packages:
name: google_maps_flutter
url: "https://pub.dartlang.org"
source: hosted
- version: "0.5.27+1"
+ version: "0.5.30"
google_maps_flutter_platform_interface:
dependency: transitive
description:
name: google_maps_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.1"
+ version: "1.0.4"
matcher:
dependency: transitive
description:
@@ -150,7 +150,7 @@ packages:
name: provider
url: "https://pub.dartlang.org"
source: hosted
- version: "4.0.5+1"
+ version: "4.3.2"
sky_engine:
dependency: transitive
description: flutter
@@ -218,7 +218,7 @@ packages:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.4"
+ version: "2.2.0"
vector_math:
dependency: transitive
description:
@@ -228,4 +228,4 @@ packages:
version: "2.0.8"
sdks:
dart: ">=2.9.0-14.0.dev <3.0.0"
- flutter: ">=1.12.13+hotfix.5 <2.0.0"
+ flutter: ">=1.16.3 <2.0.0"