Skip to content

msglist: Add message action sheet with "Share" button #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 1 addition & 0 deletions ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
40 changes: 40 additions & 0 deletions ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
platform :ios, '11.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}

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

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you say a bit about how you produced this first commit?

I tried adding just the pubspec.yaml line and then running flutter pub get and then flutter run -d <an iOS device>. That produced much of the same generated code, but with a few differences, so I wonder if you did something different.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-made the commit and wrote a proper commit message.

use_frameworks!
use_modular_headers!

flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
29 changes: 29 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
PODS:
- Flutter (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- share_plus (0.0.1):
- Flutter

DEPENDENCIES:
- Flutter (from `Flutter`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)

EXTERNAL SOURCES:
Flutter:
:path: Flutter
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"

SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68

PODFILE CHECKSUM: 985e5b058f26709dc81f9ae74ea2b2775bdbcefe

COCOAPODS: 1.11.3
68 changes: 68 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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 */; };
F311C174AF9C005CE4AADD72 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3EAE3F3F518B95B7BFEB4FE7 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand All @@ -31,9 +32,13 @@
/* Begin PBXFileReference section */
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>"; };
157DB5307E82FD55510A52F6 /* 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>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3EAE3F3F518B95B7BFEB4FE7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4541FE48FEFD549440B32652 /* 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>"; };
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>"; };
7A94C3F14B90E7FC7BC4B082 /* 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>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.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>"; };
Expand All @@ -49,12 +54,24 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F311C174AF9C005CE4AADD72 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
876166F6347BF9C555764E5A /* Pods */ = {
isa = PBXGroup;
children = (
157DB5307E82FD55510A52F6 /* Pods-Runner.debug.xcconfig */,
4541FE48FEFD549440B32652 /* Pods-Runner.release.xcconfig */,
7A94C3F14B90E7FC7BC4B082 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
Expand All @@ -72,6 +89,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
876166F6347BF9C555764E5A /* Pods */,
DE33BFABA662D9225C39B5E5 /* Frameworks */,
);
sourceTree = "<group>";
};
Expand All @@ -98,19 +117,29 @@
path = Runner;
sourceTree = "<group>";
};
DE33BFABA662D9225C39B5E5 /* Frameworks */ = {
isa = PBXGroup;
children = (
3EAE3F3F518B95B7BFEB4FE7 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
6B657AD6067C1F92BC2172DC /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
CF082B7052F068E927BF3A60 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -185,6 +214,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
6B657AD6067C1F92BC2172DC /* [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;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
Expand All @@ -200,6 +251,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
CF082B7052F068E927BF3A60 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
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 */
Expand Down
3 changes: 3 additions & 0 deletions ios/Runner.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions lib/widgets/action_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart';

import '../api/model/model.dart';
import 'draggable_scrollable_modal_bottom_sheet.dart';

void showMessageActionSheet({required BuildContext context, required Message message}) {
showDraggableScrollableModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Column(
children: [
MenuItemButton(
leadingIcon: Icon(Icons.adaptive.share),
onPressed: () async {
// Close the message action sheet; we're about to show the share
// sheet. (We could do this after the sharing Future settles, but
// on iOS I get impatient with how slowly our action sheet
// dismisses in that case.)
// TODO(#24): Fix iOS bug where this call causes the keyboard to
// reopen (if it was open at the time of this
// `showMessageActionSheet` call) and cover a large part of the
// share sheet.
Navigator.of(context).pop();

// TODO: to support iPads, we're asked to give a
// `sharePositionOrigin` param, or risk crashing / hanging:
// https://pub.dev/packages/share_plus#ipad
// Perhaps a wart in the API; discussion:
// https://github.com/zulip/zulip-flutter/pull/12#discussion_r1130146231
// TODO: Share raw Markdown, not HTML
await Share.shareWithResult(message.content);
},
child: const Text('Share'),
),
]
);
});
}
124 changes: 124 additions & 0 deletions lib/widgets/draggable_scrollable_modal_bottom_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';

class _DraggableScrollableLayer extends StatelessWidget {
const _DraggableScrollableLayer({required this.builder});

final WidgetBuilder builder;

@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
// Match `initial…` to `min…` so that a slight drag downward dismisses
// the sheet instead of just resizing it. Making them equal gives a
// buggy experience for some reason
// ( https://github.com/zulip/zulip-flutter/pull/12#discussion_r1116423455 )
// so we work around by make `initial…` a bit bigger.
minChildSize: 0.25,
initialChildSize: 0.26,
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave a comment pointing to #12 (comment) for why we're setting one of these to epsilon more than the other.


// With `expand: true`, the bottom sheet would then start out occupying
// the whole screen, as if `initialChildSize` was 1.0. That doesn't seem
// like what the docs call for. Maybe a bug. Or maybe it's somehow
// related to the `Stack`?
expand: false,

builder: (BuildContext context, ScrollController scrollController) {
return SingleChildScrollView(
// Prevent overscroll animation on swipe down; it looks
// sloppy when you're swiping to dismiss the sheet.
physics: const ClampingScrollPhysics(),

controller: scrollController,

child: Padding(
// Avoid the drag handle. See comment on
// _DragHandleLayer's SizedBox.height.
padding: const EdgeInsets.only(top: kMinInteractiveDimension),

// Extend DraggableScrollableSheet to full width so the whole
// sheet responds to drag/scroll uniformly.
child: FractionallySizedBox(
widthFactor: 1.0,
child: Builder(builder: builder),
),
),
);
});
}
}

class _DragHandleLayer extends StatelessWidget {
@override
Widget build(BuildContext context) {
ColorScheme colorScheme = Theme.of(context).colorScheme;
return SizedBox(
// In the spec, this is expressed as 22 logical pixels of top/bottom
// padding on the drag handle:
// https://m3.material.io/components/bottom-sheets/specs#e69f3dfb-e443-46ba-b4a8-aabc718cf335
// The drag handle is specified with height 4 logical pixels, so we can
// get the same result by vertically centering the handle in a box with
// height 22 + 4 + 22 = 48. We have another way to say 48 --
// kMinInteractiveDimension -- which is actually not a bad way to
// express it, since the feature was announced as "an optional drag
// handle with an accessible 48dp hit target":
// https://m3.material.io/components/bottom-sheets/overview#2cce5bae-eb83-40b0-8e52-5d0cfaa9b795
// As a bonus, that constant is easy to use at the other layer in the
// Stack where we set the starting position of the sheet's content to
// avoid the drag handle.
height: kMinInteractiveDimension,

child: Center(
child: ClipRRect(
clipBehavior: Clip.hardEdge,
borderRadius: const BorderRadius.all(Radius.circular(2)),
child: SizedBox(
// height / width / color (including opacity) from this table:
// https://m3.material.io/components/bottom-sheets/specs#7c093473-d9e1-48f3-9659-b75519c2a29d
height: 4,
width: 32,
child: ColoredBox(color: colorScheme.onSurfaceVariant.withOpacity(0.40)),
),
)));
}
}

/// Show a modal bottom sheet that drags and scrolls to present lots of content.
///
/// Aims to follow Material 3's "bottom sheet" with a drag handle:
/// https://m3.material.io/components/bottom-sheets/overview
Future<T?> showDraggableScrollableModalBottomSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
}) {
return showModalBottomSheet<T>(
context: context,

// Clip.hardEdge looks bad; Clip.antiAliasWithSaveLayer looks pixel-perfect
// on my iPhone 13 Pro but is marked as "much slower":
// https://api.flutter.dev/flutter/dart-ui/Clip.html
clipBehavior: Clip.antiAlias,
Comment on lines +96 to +99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh — on my Pixel 5, I can't actually spot a difference regardless of what value I put here. Even Clip.none. (And I even restarted, just in case I was missing something about whether hot reload would work; but still nothing changed.)

Copy link
Collaborator Author

@chrisbobbe chrisbobbe Feb 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one took some extra steps for me to reproduce. Did you test by adding enough content in the sheet that it became scrollable, then as you scrolled down, watch what happened as the content crossed the curved boundary of the sheet in the corner?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I see. No, I didn't ­— I just looked at the corners of the sheet itself, without anything scrolling into them.


// The spec:
// https://m3.material.io/components/bottom-sheets/specs
// defines the container's shape with the design token
// `md.sys.shape.corner.extra-large.top`, which in the table at
// https://m3.material.io/styles/shape/shape-scale-tokens#6f668ba1-b671-4ea2-bcf3-c1cff4f4099e
// maps to:
// 28dp,28dp,0dp,0dp
// SHAPE_FAMILY_ROUNDED_CORNERS
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(28.0))),

useSafeArea: true,
isScrollControlled: true,
builder: (BuildContext context) {
// Make the content start below the drag handle in the y-direction, but
// when the content is scrollable, let it scroll under the drag handle in
// the z-direction.
return Stack(
children: [
_DraggableScrollableLayer(builder: builder),
_DragHandleLayer(),
],
);
});
}
Loading