mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 22:09:06 +00:00
Place tracker/maintenance (#519)
* format place_tracker README * update sentence in README * sort Dart members, remove unnecessary type declarations * Run place_tracker on Android and iOS, update project files * add link to developer preview caveat * grammar * update MAINTENANCE
This commit is contained in:
@@ -12,7 +12,7 @@ match any new language/SDK features, etc.).
|
|||||||
| infinite_list | filiph | 5/13/20 |
|
| infinite_list | filiph | 5/13/20 |
|
||||||
| isolate_example | johnpryan | 11/21/19 |
|
| isolate_example | johnpryan | 11/21/19 |
|
||||||
| jsonexample | redbrogdon | 1/3/20 |
|
| jsonexample | redbrogdon | 1/3/20 |
|
||||||
| place_tracker | | |
|
| place_tracker | johnpryan | 8/13/20 |
|
||||||
| platform_channels | | |
|
| platform_channels | | |
|
||||||
| platform_design | johnpryan | 10/7/19 |
|
| platform_design | johnpryan | 10/7/19 |
|
||||||
| platform_view_swift | redbrogdon | 10/7/19 |
|
| platform_view_swift | redbrogdon | 10/7/19 |
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ of building it out. This sample currently only works on Android (see Caveat belo
|
|||||||
|
|
||||||
### `place_map.dart`
|
### `place_map.dart`
|
||||||
|
|
||||||
This page shows a full-screen GoogleMap widget with place markers. Provides examples of how
|
This page shows a full-screen GoogleMap widget with place markers. Provides
|
||||||
to stack other widgets on top of a GoogleMap widget, how to add markers to a map, and how to make
|
examples of how to stack other widgets on top of a GoogleMap widget, how to add
|
||||||
other flutter widgets interact with the GoogleMap widget.
|
markers to a map, and how to make other flutter widgets interact with the
|
||||||
|
GoogleMap widget.
|
||||||
|
|
||||||
### `place_details.dart`
|
### `place_details.dart`
|
||||||
|
|
||||||
This page shows a detailed view of a single place. Provides examples of how to place a
|
This page shows a detailed view of a single place. Provides examples of how to
|
||||||
GoogleMap widget inside of a ListView and how to disable certain touch gestures on the map.
|
place a GoogleMap widget inside of a ListView and how to disable certain touch
|
||||||
|
gestures on the map.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -32,7 +34,8 @@ To run this sample app, you will need an API key.
|
|||||||
|
|
||||||
Get an API key at <https://cloud.google.com/maps-platform/>.
|
Get an API key at <https://cloud.google.com/maps-platform/>.
|
||||||
|
|
||||||
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
|
```xml
|
||||||
<manifest ...
|
<manifest ...
|
||||||
@@ -41,14 +44,17 @@ Specify your API key in the application manifest `android/app/src/main/AndroidMa
|
|||||||
android:value="YOUR KEY HERE"/>
|
android:value="YOUR KEY HERE"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
For help getting started with Flutter, view our online
|
||||||
[documentation](https://flutter.io/).
|
[documentation](https://flutter.io/).
|
||||||
|
|
||||||
## Caveat
|
## 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
|
* Dart APIs for controlling and interacting with a GoogleMap view from Flutter
|
||||||
code are still being consolidated and expanded. The intention is to grow
|
code are still being consolidated and expanded. The intention is to grow
|
||||||
current coverage into a complete offering. Issues and pull requests aimed to
|
current coverage into a complete offering. Issues and pull requests aimed to
|
||||||
|
|||||||
@@ -10,81 +10,32 @@ project 'Runner', {
|
|||||||
'Release' => :release,
|
'Release' => :release,
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse_KV_file(file, separator='=')
|
def flutter_root
|
||||||
file_abs_path = File.expand_path(file)
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
if !File.exists? file_abs_path
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
return [];
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
end
|
end
|
||||||
generated_key_values = {}
|
|
||||||
skip_line_start_symbols = ["#", "/"]
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
File.foreach(file_abs_path) do |line|
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
|
return matches[1].strip if matches
|
||||||
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
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
target 'Runner' do
|
target 'Runner' do
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
use_modular_headers!
|
use_modular_headers!
|
||||||
|
|
||||||
# Flutter Pod
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
|
||||||
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
|
|
||||||
end
|
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|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
target.build_configurations.each do |config|
|
flutter_additional_ios_build_settings(target)
|
||||||
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,11 +9,8 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
|
60896A287CEE44DA12929F37 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AC813E96EEED8049372B007 /* Pods_Runner.framework */; };
|
||||||
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
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 */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
@@ -26,8 +23,6 @@
|
|||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
|
|
||||||
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
|
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -37,14 +32,16 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
|
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 = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
|
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
@@ -57,20 +54,36 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
|
60896A287CEE44DA12929F37 /* Pods_Runner.framework in Frameworks */,
|
||||||
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
62C8D3D83EF1F84916A2F93E /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8AC813E96EEED8049372B007 /* Pods_Runner.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3B80C3931E831B6300D905FE /* App.framework */,
|
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
9740EEBA1CF902C7004384FC /* Flutter.framework */,
|
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
@@ -84,6 +97,8 @@
|
|||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
8B9122CDB85400D9D46FA60E /* Pods */,
|
||||||
|
62C8D3D83EF1F84916A2F93E /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -125,12 +140,15 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
B606037D737D7C74CC55B500 /* [CP] Check Pods Manifest.lock */,
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
CAF79D024134EA0C14FDEDC4 /* [CP] Embed Pods Frameworks */,
|
||||||
|
2FBA60F1C5E1C14B292C896D /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -189,6 +207,24 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase 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 */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -201,7 +237,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
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 */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
@@ -217,6 +253,46 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
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 */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -253,7 +329,6 @@
|
|||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
@@ -330,7 +405,6 @@
|
|||||||
};
|
};
|
||||||
97C147031CF9000F007C117D /* Debug */ = {
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
@@ -386,7 +460,6 @@
|
|||||||
};
|
};
|
||||||
97C147041CF9000F007C117D /* Release */ = {
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
|||||||
@@ -4,4 +4,7 @@
|
|||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
|
||||||
enum PlaceCategory {
|
|
||||||
favorite,
|
|
||||||
visited,
|
|
||||||
wantToGo,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Place {
|
class Place {
|
||||||
|
final String id;
|
||||||
|
final LatLng latLng;
|
||||||
|
final String name;
|
||||||
|
final PlaceCategory category;
|
||||||
|
final String description;
|
||||||
|
final int starRating;
|
||||||
|
|
||||||
const Place({
|
const Place({
|
||||||
@required this.id,
|
@required this.id,
|
||||||
@required this.latLng,
|
@required this.latLng,
|
||||||
@@ -21,13 +22,6 @@ class Place {
|
|||||||
assert(category != null),
|
assert(category != null),
|
||||||
assert(starRating != null && starRating >= 0 && starRating <= 5);
|
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 latitude => latLng.latitude;
|
||||||
|
|
||||||
double get longitude => latLng.longitude;
|
double get longitude => latLng.longitude;
|
||||||
@@ -50,3 +44,9 @@ class Place {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PlaceCategory {
|
||||||
|
favorite,
|
||||||
|
visited,
|
||||||
|
wantToGo,
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import 'place.dart';
|
|||||||
import 'stub_data.dart';
|
import 'stub_data.dart';
|
||||||
|
|
||||||
class PlaceDetails extends StatefulWidget {
|
class PlaceDetails extends StatefulWidget {
|
||||||
|
final Place place;
|
||||||
|
final ValueChanged<Place> onChanged;
|
||||||
|
|
||||||
const PlaceDetails({
|
const PlaceDetails({
|
||||||
@required this.place,
|
@required this.place,
|
||||||
@required this.onChanged,
|
@required this.onChanged,
|
||||||
@@ -15,9 +18,6 @@ class PlaceDetails extends StatefulWidget {
|
|||||||
assert(onChanged != null),
|
assert(onChanged != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
final Place place;
|
|
||||||
final ValueChanged<Place> onChanged;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PlaceDetailsState createState() => PlaceDetailsState();
|
PlaceDetailsState createState() => PlaceDetailsState();
|
||||||
}
|
}
|
||||||
@@ -26,10 +26,37 @@ class PlaceDetailsState extends State<PlaceDetails> {
|
|||||||
Place _place;
|
Place _place;
|
||||||
GoogleMapController _mapController;
|
GoogleMapController _mapController;
|
||||||
final Set<Marker> _markers = {};
|
final Set<Marker> _markers = {};
|
||||||
|
|
||||||
final TextEditingController _nameController = TextEditingController();
|
final TextEditingController _nameController = TextEditingController();
|
||||||
final TextEditingController _descriptionController = 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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_place = widget.place;
|
_place = widget.place;
|
||||||
@@ -38,20 +65,10 @@ class PlaceDetailsState extends State<PlaceDetails> {
|
|||||||
return super.initState();
|
return super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onMapCreated(GoogleMapController controller) {
|
|
||||||
_mapController = controller;
|
|
||||||
setState(() {
|
|
||||||
_markers.add(Marker(
|
|
||||||
markerId: MarkerId(_place.latLng.toString()),
|
|
||||||
position: _place.latLng,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _detailsBody() {
|
Widget _detailsBody() {
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(24.0, 12.0, 24.0, 12.0),
|
padding: const EdgeInsets.fromLTRB(24.0, 12.0, 24.0, 12.0),
|
||||||
children: <Widget>[
|
children: [
|
||||||
_NameTextField(
|
_NameTextField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -87,68 +104,21 @@ class PlaceDetailsState extends State<PlaceDetails> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _onMapCreated(GoogleMapController controller) {
|
||||||
Widget build(BuildContext context) {
|
_mapController = controller;
|
||||||
return Scaffold(
|
setState(() {
|
||||||
appBar: AppBar(
|
_markers.add(Marker(
|
||||||
title: Text('${_place.name}'),
|
markerId: MarkerId(_place.latLng.toString()),
|
||||||
backgroundColor: Colors.green[700],
|
position: _place.latLng,
|
||||||
actions: <Widget>[
|
));
|
||||||
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(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NameTextField extends StatelessWidget {
|
|
||||||
const _NameTextField({
|
|
||||||
@required this.controller,
|
|
||||||
@required this.onChanged,
|
|
||||||
Key key,
|
|
||||||
}) : assert(controller != null),
|
|
||||||
assert(onChanged != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final TextEditingController controller;
|
|
||||||
final ValueChanged<String> 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 {
|
class _DescriptionTextField extends StatelessWidget {
|
||||||
|
final TextEditingController controller;
|
||||||
|
|
||||||
|
final ValueChanged<String> onChanged;
|
||||||
const _DescriptionTextField({
|
const _DescriptionTextField({
|
||||||
@required this.controller,
|
@required this.controller,
|
||||||
@required this.onChanged,
|
@required this.onChanged,
|
||||||
@@ -157,9 +127,6 @@ class _DescriptionTextField extends StatelessWidget {
|
|||||||
assert(onChanged != null),
|
assert(onChanged != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
final TextEditingController controller;
|
|
||||||
final ValueChanged<String> onChanged;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
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<int> 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 {
|
class _Map extends StatelessWidget {
|
||||||
|
final LatLng center;
|
||||||
|
|
||||||
|
final GoogleMapController mapController;
|
||||||
|
final ArgumentCallback<GoogleMapController> onMapCreated;
|
||||||
|
final Set<Marker> markers;
|
||||||
const _Map({
|
const _Map({
|
||||||
@required this.center,
|
@required this.center,
|
||||||
@required this.mapController,
|
@required this.mapController,
|
||||||
@@ -223,24 +164,19 @@ class _Map extends StatelessWidget {
|
|||||||
assert(onMapCreated != null),
|
assert(onMapCreated != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
final LatLng center;
|
|
||||||
final GoogleMapController mapController;
|
|
||||||
final ArgumentCallback<GoogleMapController> onMapCreated;
|
|
||||||
final Set<Marker> markers;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
elevation: 4.0,
|
elevation: 4,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 340.0,
|
width: 340,
|
||||||
height: 240.0,
|
height: 240,
|
||||||
child: GoogleMap(
|
child: GoogleMap(
|
||||||
onMapCreated: onMapCreated,
|
onMapCreated: onMapCreated,
|
||||||
initialCameraPosition: CameraPosition(
|
initialCameraPosition: CameraPosition(
|
||||||
target: center,
|
target: center,
|
||||||
zoom: 16.0,
|
zoom: 16,
|
||||||
),
|
),
|
||||||
markers: markers,
|
markers: markers,
|
||||||
zoomGesturesEnabled: false,
|
zoomGesturesEnabled: false,
|
||||||
@@ -253,78 +189,55 @@ class _Map extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _NameTextField extends StatelessWidget {
|
||||||
|
final TextEditingController controller;
|
||||||
|
|
||||||
|
final ValueChanged<String> 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 {
|
class _Reviews extends StatelessWidget {
|
||||||
const _Reviews({
|
const _Reviews({
|
||||||
Key key,
|
Key key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
Widget _buildSingleReview(String reviewText) {
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: 80.0,
|
|
||||||
height: 80.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(40.0),
|
|
||||||
border: Border.all(
|
|
||||||
width: 3.0,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: const <Widget>[
|
|
||||||
Text(
|
|
||||||
'5',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24.0,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.star,
|
|
||||||
color: Colors.amber,
|
|
||||||
size: 36.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16.0),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
reviewText,
|
|
||||||
style: const TextStyle(fontSize: 20.0, color: Colors.black87),
|
|
||||||
maxLines: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(
|
|
||||||
height: 8.0,
|
|
||||||
color: Colors.grey[700],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: [
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 8.0),
|
padding: EdgeInsets.fromLTRB(0, 12, 0, 8),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Reviews',
|
'Reviews',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24.0,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
@@ -340,4 +253,90 @@ class _Reviews extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildSingleReview(String reviewText) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(40),
|
||||||
|
border: Border.all(
|
||||||
|
width: 3,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: const [
|
||||||
|
Text(
|
||||||
|
'5',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.star,
|
||||||
|
color: Colors.amber,
|
||||||
|
size: 36,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
reviewText,
|
||||||
|
style: const TextStyle(fontSize: 20, color: Colors.black87),
|
||||||
|
maxLines: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 8,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StarBar extends StatelessWidget {
|
||||||
|
static const int maxStars = 5;
|
||||||
|
|
||||||
|
final int rating;
|
||||||
|
final ValueChanged<int> 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 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,27 +15,11 @@ class PlaceList extends StatefulWidget {
|
|||||||
class PlaceListState extends State<PlaceList> {
|
class PlaceListState extends State<PlaceList> {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
void _onCategoryChanged(PlaceCategory newCategory) {
|
|
||||||
_scrollController.jumpTo(0.0);
|
|
||||||
Provider.of<AppState>(context, listen: false)
|
|
||||||
.setSelectedCategory(newCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onPlaceChanged(Place value) {
|
|
||||||
// Replace the place with the modified version.
|
|
||||||
final newPlaces =
|
|
||||||
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
|
|
||||||
final index = newPlaces.indexWhere((place) => place.id == value.id);
|
|
||||||
newPlaces[index] = value;
|
|
||||||
|
|
||||||
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var state = Provider.of<AppState>(context);
|
var state = Provider.of<AppState>(context);
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: [
|
||||||
_ListCategoryButtonBar(
|
_ListCategoryButtonBar(
|
||||||
selectedCategory: state.selectedCategory,
|
selectedCategory: state.selectedCategory,
|
||||||
onCategoryChanged: (value) => _onCategoryChanged(value),
|
onCategoryChanged: (value) => _onCategoryChanged(value),
|
||||||
@@ -57,113 +41,29 @@ class PlaceListState extends State<PlaceList> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class _PlaceListTile extends StatelessWidget {
|
void _onCategoryChanged(PlaceCategory newCategory) {
|
||||||
const _PlaceListTile({
|
_scrollController.jumpTo(0.0);
|
||||||
Key key,
|
Provider.of<AppState>(context, listen: false)
|
||||||
@required this.place,
|
.setSelectedCategory(newCategory);
|
||||||
@required this.onPlaceChanged,
|
|
||||||
}) : assert(place != null),
|
|
||||||
assert(onPlaceChanged != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final Place place;
|
|
||||||
final ValueChanged<Place> onPlaceChanged;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => Navigator.push<void>(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) {
|
|
||||||
return PlaceDetails(
|
|
||||||
place: place,
|
|
||||||
onChanged: (value) => onPlaceChanged(value),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(top: 16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
place.name,
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 20.0,
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: List.generate(5, (index) {
|
|
||||||
return Icon(
|
|
||||||
Icons.star,
|
|
||||||
size: 28.0,
|
|
||||||
color: place.starRating > index
|
|
||||||
? Colors.amber
|
|
||||||
: Colors.grey[400],
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
place.description ?? '',
|
|
||||||
style: Theme.of(context).textTheme.subtitle1,
|
|
||||||
maxLines: 4,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
|
||||||
Divider(
|
|
||||||
height: 2.0,
|
|
||||||
color: Colors.grey[700],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class _ListCategoryButtonBar extends StatelessWidget {
|
void _onPlaceChanged(Place value) {
|
||||||
const _ListCategoryButtonBar({
|
// Replace the place with the modified version.
|
||||||
Key key,
|
final newPlaces =
|
||||||
@required this.selectedCategory,
|
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
|
||||||
@required this.onCategoryChanged,
|
final index = newPlaces.indexWhere((place) => place.id == value.id);
|
||||||
}) : assert(selectedCategory != null),
|
newPlaces[index] = value;
|
||||||
assert(onCategoryChanged != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final PlaceCategory selectedCategory;
|
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
|
||||||
final ValueChanged<PlaceCategory> onCategoryChanged;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: <Widget>[
|
|
||||||
_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 {
|
class _CategoryButton extends StatelessWidget {
|
||||||
|
final PlaceCategory category;
|
||||||
|
|
||||||
|
final bool selected;
|
||||||
|
final ValueChanged<PlaceCategory> onCategoryChanged;
|
||||||
const _CategoryButton({
|
const _CategoryButton({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.category,
|
@required this.category,
|
||||||
@@ -173,10 +73,6 @@ class _CategoryButton extends StatelessWidget {
|
|||||||
assert(selected != null),
|
assert(selected != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
final PlaceCategory category;
|
|
||||||
final bool selected;
|
|
||||||
final ValueChanged<PlaceCategory> onCategoryChanged;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String _buttonText;
|
String _buttonText;
|
||||||
@@ -216,3 +112,107 @@ class _CategoryButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ListCategoryButtonBar extends StatelessWidget {
|
||||||
|
final PlaceCategory selectedCategory;
|
||||||
|
|
||||||
|
final ValueChanged<PlaceCategory> 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<Place> onPlaceChanged;
|
||||||
|
const _PlaceListTile({
|
||||||
|
Key key,
|
||||||
|
@required this.place,
|
||||||
|
@required this.onPlaceChanged,
|
||||||
|
}) : assert(place != null),
|
||||||
|
assert(onPlaceChanged != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => Navigator.push<void>(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return PlaceDetails(
|
||||||
|
place: place,
|
||||||
|
onChanged: (value) => onPlaceChanged(value),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(top: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
place.name,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20.0,
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
return Icon(
|
||||||
|
Icons.star,
|
||||||
|
size: 28.0,
|
||||||
|
color: place.starRating > index
|
||||||
|
? Colors.amber
|
||||||
|
: Colors.grey[400],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
place.description ?? '',
|
||||||
|
style: Theme.of(context).textTheme.subtitle1,
|
||||||
|
maxLines: 4,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Divider(
|
||||||
|
height: 2.0,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,41 +11,55 @@ import 'place.dart';
|
|||||||
import 'place_details.dart';
|
import 'place_details.dart';
|
||||||
import 'place_tracker_app.dart';
|
import 'place_tracker_app.dart';
|
||||||
|
|
||||||
|
class MapConfiguration {
|
||||||
|
final List<Place> 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 {
|
class PlaceMap extends StatefulWidget {
|
||||||
|
final LatLng center;
|
||||||
|
|
||||||
const PlaceMap({
|
const PlaceMap({
|
||||||
Key key,
|
Key key,
|
||||||
this.center,
|
this.center,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final LatLng center;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PlaceMapState createState() => PlaceMapState();
|
PlaceMapState createState() => PlaceMapState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlaceMapState extends State<PlaceMap> {
|
class PlaceMapState extends State<PlaceMap> {
|
||||||
static Future<BitmapDescriptor> _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<Place> _getPlacesForCategory(
|
|
||||||
PlaceCategory category, List<Place> places) {
|
|
||||||
return places.where((place) => place.category == category).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer<GoogleMapController> mapController = Completer();
|
Completer<GoogleMapController> mapController = Completer();
|
||||||
|
|
||||||
MapType _currentMapType = MapType.normal;
|
MapType _currentMapType = MapType.normal;
|
||||||
@@ -60,6 +74,50 @@ class PlaceMapState extends State<PlaceMap> {
|
|||||||
|
|
||||||
MapConfiguration _configuration;
|
MapConfiguration _configuration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_maybeUpdateMapConfiguration();
|
||||||
|
var state = Provider.of<AppState>(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<void> onMapCreated(GoogleMapController controller) async {
|
Future<void> onMapCreated(GoogleMapController controller) async {
|
||||||
mapController.complete(controller);
|
mapController.complete(controller);
|
||||||
_lastMapPosition = widget.center;
|
_lastMapPosition = widget.center;
|
||||||
@@ -83,152 +141,13 @@ class PlaceMapState extends State<PlaceMap> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Marker> _createPlaceMarker(BuildContext context, Place place) async {
|
void _cancelAddPlace() {
|
||||||
final marker = Marker(
|
if (_pendingMarker != null) {
|
||||||
markerId: MarkerId(place.latLng.toString()),
|
setState(() {
|
||||||
position: place.latLng,
|
_markers.remove(_pendingMarker);
|
||||||
infoWindow: InfoWindow(
|
_pendingMarker = null;
|
||||||
title: place.name,
|
});
|
||||||
snippet: '${place.starRating} Star Rating',
|
|
||||||
onTap: () => _pushPlaceDetailsScreen(place),
|
|
||||||
),
|
|
||||||
icon: await _getPlaceMarkerIcon(context, place.category),
|
|
||||||
visible: place.category ==
|
|
||||||
Provider.of<AppState>(context, listen: false).selectedCategory,
|
|
||||||
);
|
|
||||||
_markedPlaces[marker] = place;
|
|
||||||
return marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _pushPlaceDetailsScreen(Place place) {
|
|
||||||
assert(place != null);
|
|
||||||
|
|
||||||
Navigator.push<void>(
|
|
||||||
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<Place>.from(Provider.of<AppState>(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<AppState>(context, listen: false).selectedCategory,
|
|
||||||
);
|
|
||||||
|
|
||||||
Provider.of<AppState>(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<void> _switchSelectedCategory(PlaceCategory category) async {
|
|
||||||
Provider.of<AppState>(context, listen: false).setSelectedCategory(category);
|
|
||||||
await _showPlacesForSelectedCategory(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _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<void> _zoomToFitPlaces(List<Place> 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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _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<void> _confirmAddPlace(BuildContext context) async {
|
Future<void> _confirmAddPlace(BuildContext context) async {
|
||||||
@@ -298,22 +217,21 @@ class PlaceMapState extends State<PlaceMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cancelAddPlace() {
|
Future<Marker> _createPlaceMarker(BuildContext context, Place place) async {
|
||||||
if (_pendingMarker != null) {
|
final marker = Marker(
|
||||||
setState(() {
|
markerId: MarkerId(place.latLng.toString()),
|
||||||
_markers.remove(_pendingMarker);
|
position: place.latLng,
|
||||||
_pendingMarker = null;
|
infoWindow: InfoWindow(
|
||||||
});
|
title: place.name,
|
||||||
}
|
snippet: '${place.starRating} Star Rating',
|
||||||
}
|
onTap: () => _pushPlaceDetailsScreen(place),
|
||||||
|
),
|
||||||
void _onToggleMapTypePressed() {
|
icon: await _getPlaceMarkerIcon(context, place.category),
|
||||||
final nextType =
|
visible: place.category ==
|
||||||
MapType.values[(_currentMapType.index + 1) % MapType.values.length];
|
Provider.of<AppState>(context, listen: false).selectedCategory,
|
||||||
|
);
|
||||||
setState(() {
|
_markedPlaces[marker] = place;
|
||||||
_currentMapType = nextType;
|
return marker;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _maybeUpdateMapConfiguration() async {
|
Future<void> _maybeUpdateMapConfiguration() async {
|
||||||
@@ -351,65 +269,183 @@ class PlaceMapState extends State<PlaceMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> _onAddPlacePressed() async {
|
||||||
Widget build(BuildContext context) {
|
setState(() {
|
||||||
_maybeUpdateMapConfiguration();
|
final newMarker = Marker(
|
||||||
var state = Provider.of<AppState>(context);
|
markerId: MarkerId(_lastMapPosition.toString()),
|
||||||
|
position: _lastMapPosition,
|
||||||
|
infoWindow: InfoWindow(title: 'New Place'),
|
||||||
|
draggable: true,
|
||||||
|
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
|
||||||
|
);
|
||||||
|
_markers.add(newMarker);
|
||||||
|
_pendingMarker = newMarker;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Builder(builder: (context) {
|
void _onPlaceChanged(Place value) {
|
||||||
// We need this additional builder here so that we can pass its context to
|
// Replace the place with the modified version.
|
||||||
// _AddPlaceButtonBar's onSavePressed callback. This callback shows a
|
final newPlaces =
|
||||||
// SnackBar and to do this, we need a build context that has Scaffold as
|
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
|
||||||
// an ancestor.
|
final index = newPlaces.indexWhere((place) => place.id == value.id);
|
||||||
return Center(
|
newPlaces[index] = value;
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
_updateExistingPlaceMarker(place: value);
|
||||||
GoogleMap(
|
|
||||||
onMapCreated: onMapCreated,
|
// Manually update our map configuration here since our map is already
|
||||||
initialCameraPosition: CameraPosition(
|
// updated with the new marker. Otherwise, the map would be reconfigured
|
||||||
target: widget.center,
|
// in the main build method due to a modified AppState.
|
||||||
zoom: 11.0,
|
_configuration = MapConfiguration(
|
||||||
),
|
places: newPlaces,
|
||||||
mapType: _currentMapType,
|
selectedCategory:
|
||||||
markers: _markers,
|
Provider.of<AppState>(context, listen: false).selectedCategory,
|
||||||
onCameraMove: (position) => _lastMapPosition = position.target,
|
);
|
||||||
),
|
|
||||||
_CategoryButtonBar(
|
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
|
||||||
selectedPlaceCategory: state.selectedCategory,
|
}
|
||||||
visible: _pendingMarker == null,
|
|
||||||
onChanged: _switchSelectedCategory,
|
void _onToggleMapTypePressed() {
|
||||||
),
|
final nextType =
|
||||||
_AddPlaceButtonBar(
|
MapType.values[(_currentMapType.index + 1) % MapType.values.length];
|
||||||
visible: _pendingMarker != null,
|
|
||||||
onSavePressed: () => _confirmAddPlace(context),
|
setState(() {
|
||||||
onCancelPressed: _cancelAddPlace,
|
_currentMapType = nextType;
|
||||||
),
|
});
|
||||||
_MapFabs(
|
}
|
||||||
visible: _pendingMarker == null,
|
|
||||||
onAddPlacePressed: _onAddPlacePressed,
|
void _pushPlaceDetailsScreen(Place place) {
|
||||||
onToggleMapTypePressed: _onToggleMapTypePressed,
|
assert(place != null);
|
||||||
),
|
|
||||||
],
|
Navigator.push<void>(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return PlaceDetails(
|
||||||
|
place: place,
|
||||||
|
onChanged: (value) => _onPlaceChanged(value),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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<void> _switchSelectedCategory(PlaceCategory category) async {
|
||||||
|
Provider.of<AppState>(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<void> _zoomToFitPlaces(List<Place> 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<BitmapDescriptor> _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<Place> _getPlacesForCategory(
|
||||||
|
PlaceCategory category, List<Place> places) {
|
||||||
|
return places.where((place) => place.category == category).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CategoryButtonBar extends StatelessWidget {
|
class _AddPlaceButtonBar extends StatelessWidget {
|
||||||
const _CategoryButtonBar({
|
|
||||||
Key key,
|
|
||||||
@required this.selectedPlaceCategory,
|
|
||||||
@required this.visible,
|
|
||||||
@required this.onChanged,
|
|
||||||
}) : assert(selectedPlaceCategory != null),
|
|
||||||
assert(visible != null),
|
|
||||||
assert(onChanged != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final PlaceCategory selectedPlaceCategory;
|
|
||||||
final bool visible;
|
final bool visible;
|
||||||
final ValueChanged<PlaceCategory> onChanged;
|
|
||||||
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -420,7 +456,55 @@ class _CategoryButtonBar extends StatelessWidget {
|
|||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: ButtonBar(
|
child: ButtonBar(
|
||||||
alignment: MainAxisAlignment.center,
|
alignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CategoryButtonBar extends StatelessWidget {
|
||||||
|
final PlaceCategory selectedPlaceCategory;
|
||||||
|
final bool visible;
|
||||||
|
final ValueChanged<PlaceCategory> onChanged;
|
||||||
|
|
||||||
|
const _CategoryButtonBar({
|
||||||
|
Key key,
|
||||||
|
@required this.selectedPlaceCategory,
|
||||||
|
@required this.visible,
|
||||||
|
@required this.onChanged,
|
||||||
|
}) : assert(selectedPlaceCategory != null),
|
||||||
|
assert(visible != null),
|
||||||
|
assert(onChanged != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@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(
|
RaisedButton(
|
||||||
color: selectedPlaceCategory == PlaceCategory.favorite
|
color: selectedPlaceCategory == PlaceCategory.favorite
|
||||||
? Colors.green[700]
|
? 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);
|
|
||||||
|
|
||||||
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: <Widget>[
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MapFabs extends StatelessWidget {
|
class _MapFabs extends StatelessWidget {
|
||||||
|
final bool visible;
|
||||||
|
final VoidCallback onAddPlacePressed;
|
||||||
|
final VoidCallback onToggleMapTypePressed;
|
||||||
|
|
||||||
const _MapFabs({
|
const _MapFabs({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.visible,
|
@required this.visible,
|
||||||
@@ -517,10 +557,6 @@ class _MapFabs extends StatelessWidget {
|
|||||||
assert(onToggleMapTypePressed != null),
|
assert(onToggleMapTypePressed != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
final bool visible;
|
|
||||||
final VoidCallback onAddPlacePressed;
|
|
||||||
final VoidCallback onToggleMapTypePressed;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -529,7 +565,7 @@ class _MapFabs extends StatelessWidget {
|
|||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: visible,
|
visible: visible,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: [
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
heroTag: 'add_place_button',
|
heroTag: 'add_place_button',
|
||||||
onPressed: onAddPlacePressed,
|
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<Place> 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: const <Widget>[
|
children: const [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0),
|
padding: EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0),
|
||||||
child: Icon(Icons.pin_drop, size: 24.0),
|
child: Icon(Icons.pin_drop, size: 24.0),
|
||||||
@@ -40,7 +40,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.green[700],
|
backgroundColor: Colors.green[700],
|
||||||
actions: <Widget>[
|
actions: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0),
|
padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@@ -63,7 +63,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: state.viewType == PlaceTrackerViewType.map ? 0 : 1,
|
index: state.viewType == PlaceTrackerViewType.map ? 0 : 1,
|
||||||
children: <Widget>[
|
children: [
|
||||||
PlaceMap(center: const LatLng(45.521563, -122.677433)),
|
PlaceMap(center: const LatLng(45.521563, -122.677433)),
|
||||||
PlaceList()
|
PlaceList()
|
||||||
],
|
],
|
||||||
@@ -102,7 +102,6 @@ class AppState extends ChangeNotifier {
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
if (other.runtimeType != runtimeType) return false;
|
|
||||||
return other is AppState &&
|
return other is AppState &&
|
||||||
other.places == places &&
|
other.places == places &&
|
||||||
other.selectedCategory == selectedCategory &&
|
other.selectedCategory == selectedCategory &&
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class StubData {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
static const List<String> reviewStrings = [
|
static const reviewStrings = <String>[
|
||||||
'My favorite place in Portland. The employees are wonderful and so is the food. I go here at least once a month!',
|
'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.',
|
'Staff was very friendly. Great atmosphere and good music. Would reccommend.',
|
||||||
'Best. Place. In. Town. Period.'
|
'Best. Place. In. Town. Period.'
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ packages:
|
|||||||
name: crypto
|
name: crypto
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.5"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -82,7 +82,7 @@ packages:
|
|||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.7"
|
version: "1.0.8"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -94,14 +94,14 @@ packages:
|
|||||||
name: google_maps_flutter
|
name: google_maps_flutter
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.27+1"
|
version: "0.5.30"
|
||||||
google_maps_flutter_platform_interface:
|
google_maps_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: google_maps_flutter_platform_interface
|
name: google_maps_flutter_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.4"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -150,7 +150,7 @@ packages:
|
|||||||
name: provider
|
name: provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.5+1"
|
version: "4.3.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -218,7 +218,7 @@ packages:
|
|||||||
name: uuid
|
name: uuid
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.2.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -228,4 +228,4 @@ packages:
|
|||||||
version: "2.0.8"
|
version: "2.0.8"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.9.0-14.0.dev <3.0.0"
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user