Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 3b8f14b

Browse files
hartezSkyeHoefling
authored andcommitted
Spike of Popup feature (with implementation for iOS)
1 parent f80f301 commit 3b8f14b

File tree

13 files changed

+496
-4
lines changed

13 files changed

+496
-4
lines changed

Xamarin.Forms.Controls/CoreGallery.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ public override string ToString()
310310
new GalleryPageFactory(() => new TwoPaneViewGallery(), "TwoPaneView Gallery"),
311311
new GalleryPageFactory(() => new FlowDirectionGalleryLandingPage(), "FlowDirection"),
312312
new GalleryPageFactory(() => new AutomationPropertiesGallery(), "Accessibility"),
313+
new GalleryPageFactory(() => new PopoverGallery(), "Popover Gallery"),
314+
new GalleryPageFactory(() => new AlertGallery(), "DisplayAlert Gallery"),
313315
new GalleryPageFactory(() => new PlatformSpecificsGallery(), "Platform Specifics"),
314316
new GalleryPageFactory(() => new NativeBindingGalleryPage(), "Native Binding Controls Gallery"),
315317
new GalleryPageFactory(() => new XamlNativeViews(), "Xaml Native Views Gallery"),
@@ -328,7 +330,7 @@ public override string ToString()
328330
new GalleryPageFactory(() => new AutomationIdGallery(), "AutomationID Gallery"),
329331
new GalleryPageFactory(() => new LayoutPerformanceGallery(), "Layout Perf Gallery"),
330332
new GalleryPageFactory(() => new ListViewSelectionColor(), "ListView SelectionColor Gallery"),
331-
new GalleryPageFactory(() => new AlertGallery(), "DisplayAlert Gallery"),
333+
332334
new GalleryPageFactory(() => new ToolbarItems(), "ToolbarItems Gallery"),
333335
new GalleryPageFactory(() => new ActionSheetGallery(), "ActionSheet Gallery"),
334336
new GalleryPageFactory(() => new ActivityIndicatorCoreGalleryPage(), "ActivityIndicator Gallery"),
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace Xamarin.Forms.Controls
5+
{
6+
public class PopoverGallery : ContentPage
7+
{
8+
const string Placeholder =
9+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ante dolor, maximus non dignissim non, pellentesque id felis. Etiam accumsan leo et eleifend efficitur. Donec rutrum euismod auctor. Integer metus ante, blandit eget nisl eget, egestas imperdiet libero. Sed lectus purus, placerat quis pretium nec, ullamcorper a orci. Duis eget varius purus, et mollis metus. Sed sed mi vitae justo placerat venenatis ut sit amet sem. Etiam nec neque sit amet tellus mollis faucibus. Aliquam nec urna at leo imperdiet consectetur. Quisque turpis diam, feugiat eu maximus vel, elementum mattis sem.";
10+
11+
const string ResultTitle = "Popup Result";
12+
const string DismissText = "Cool, thanks!";
13+
14+
public PopoverGallery ()
15+
{
16+
var layout = new Grid { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill };
17+
18+
layout.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
19+
layout.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
20+
21+
var top = new StackLayout { Children = { PopoverWithLabel(), PopoverWithLayout(), TermsOfServicePopup() } };
22+
Grid.SetRow(top, 0);
23+
24+
// Putting one of the buttons on the bottom so if we're on the iPad we can see the little popover arrows working
25+
var bottom = new StackLayout { Children = { PopoverWithDatePicker() } };
26+
Grid.SetRow(bottom, 1);
27+
28+
layout.Children.Add(top);
29+
layout.Children.Add(bottom);
30+
31+
Content = layout;
32+
}
33+
34+
Button CreateButton(string text, string automationId, View content)
35+
{
36+
var button = new Button { Text = text, AutomationId = automationId };
37+
button.Clicked += async (sender, e) =>
38+
{
39+
var popover = new Popup(content, text, anchor: button);
40+
41+
var result = await Navigation.ShowPopup(popover);
42+
43+
await DisplayAlert(ResultTitle, result.ToString(), DismissText);
44+
};
45+
46+
return button;
47+
}
48+
49+
Button PopoverWithLabel()
50+
{
51+
return CreateButton("Popup with Label", "testPopupWithLabel",
52+
new Label
53+
{
54+
Text = "This is just a Label inside of a Popup. This is a basic popup which takes a View, and is only light-dismissable.",
55+
LineBreakMode = LineBreakMode.WordWrap,
56+
HorizontalTextAlignment = TextAlignment.Center,
57+
VerticalTextAlignment = TextAlignment.Center,
58+
HorizontalOptions = LayoutOptions.Fill,
59+
VerticalOptions = LayoutOptions.Fill
60+
});
61+
}
62+
63+
Button PopoverWithLayout()
64+
{
65+
var content = new StackLayout
66+
{
67+
Margin = new Thickness(20),
68+
Children =
69+
{
70+
new Image { Source = "coffee.png", Margin = new Thickness(5)},
71+
new Label { LineBreakMode = LineBreakMode.WordWrap, Margin = new Thickness(5), Text = "This is a popup with a Layout as its content." },
72+
new Label { LineBreakMode = LineBreakMode.WordWrap, Text = Placeholder }
73+
}
74+
};
75+
76+
return CreateButton("Popup with Layout", "testPopupWithLayout", content);
77+
}
78+
79+
80+
Button PopoverWithDatePicker()
81+
{
82+
var button = new Button { Text = "Popup with DatePicker" };
83+
84+
button.Clicked += async (sender, e) =>
85+
{
86+
// Create a DateChooserPopup which uses DateChooserPopupControl as its content and is anchored to this button
87+
var popup = new DateChooserPopup(new DateChooserPopupControl(), "Select Date", button);
88+
89+
// Show the popup and await the DateTime? result
90+
DateTime? result = await Navigation.ShowPopup(popup);
91+
92+
// Display the user's selection
93+
await DisplayAlert(ResultTitle, result?.ToString(), DismissText);
94+
};
95+
96+
return button;
97+
}
98+
99+
class DateChooserPopup : Popup<DateTime?>
100+
{
101+
public DateChooserPopup(IPopupView<DateTime?> popupView, string title = null, View anchor = null)
102+
: base(popupView, title, false, anchor)
103+
{
104+
// Note that this popup is not light-dismissable
105+
}
106+
107+
protected override DateTime? OnLightDismissed()
108+
{
109+
return null;
110+
}
111+
}
112+
113+
class DateChooserPopupControl : ContentView, IPopupView<DateTime?>
114+
{
115+
public View View => this;
116+
Action<DateTime?> _dismiss;
117+
118+
public void SetDismissDelegate(Action<DateTime?> dismissDelegate)
119+
{
120+
// Keep track of the dismiss delegate so we can tell the popup what the user selects
121+
_dismiss = dismissDelegate;
122+
}
123+
124+
public DateChooserPopupControl()
125+
{
126+
// Build the UI for our popup
127+
var datePicker = new DatePicker { HorizontalOptions = LayoutOptions.Center };
128+
var button = new Button { Text = "OK", HorizontalOptions = LayoutOptions.Center };
129+
130+
Content = new StackLayout
131+
{
132+
Margin = new Thickness(40),
133+
Children =
134+
{
135+
new Label
136+
{
137+
Text = "Choose a Date",
138+
HorizontalOptions = LayoutOptions.Center,
139+
HorizontalTextAlignment = TextAlignment.Center
140+
},
141+
datePicker,
142+
button
143+
}
144+
};
145+
146+
// When the user clicks OK, we report the result back to the popup
147+
button.Clicked += (sender, args) => { _dismiss?.Invoke(datePicker.Date); };
148+
}
149+
}
150+
151+
Button TermsOfServicePopup()
152+
{
153+
var button = new Button { Text = "Terms of Service Popup" };
154+
155+
// Define the content which goes into the popup (in this case, a scrollview with some really long text to read)
156+
var tos = new Label
157+
{
158+
LineBreakMode = LineBreakMode.WordWrap,
159+
Text = "This example demonstrates a convenience method for creating a popup which returns a binary result (e.g. yes/no, accept/reject, ok/cancel).\n\n"
160+
+ string.Concat(Enumerable.Repeat(Placeholder + "\n", 10))
161+
};
162+
163+
var scrollView = new ScrollView { Content = tos, Margin = new Thickness(20) };
164+
165+
// Create the popup, specifying the text for the buttons
166+
var tosPopup = new BinaryResultPopup(scrollView, "Terms of Service", anchor: button, affirmativeText: "Accept", negativeText: "Reject", size: new Size(400, 500));
167+
168+
button.Clicked += async (sender, args) =>
169+
{
170+
// Reset the popup in case we've already gotten a result from it
171+
tosPopup.Reset();
172+
173+
// Display the popup and await the result
174+
var result = await Navigation.ShowPopup(tosPopup);
175+
176+
// Show the result that we just got back from the popup
177+
await DisplayAlert(ResultTitle, result.ToString(), DismissText);
178+
};
179+
180+
return button;
181+
}
182+
}
183+
}

Xamarin.Forms.Core.UnitTests/NavigationProxyTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ public void InsertPageBefore (Page page, Page before)
7979
{
8080
}
8181

82+
public Task<T> ShowPopup<T>(Popup<T> popup)
83+
{
84+
throw new NotImplementedException();
85+
}
86+
8287
public System.Collections.Generic.IReadOnlyList<Page> NavigationStack
8388
{
8489
get { return new List<Page> (); }

Xamarin.Forms.Core/INavigation.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@ public interface INavigation
2424
Task PushModalAsync(Page page, bool animated);
2525

2626
void RemovePage(Page page);
27+
28+
Task<T> ShowPopup<T>(Popup<T> popup);
2729
}
2830
}

Xamarin.Forms.Core/NavigationProxy.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,5 +244,14 @@ Page PopModal()
244244
list.RemoveAt(list.Count - 1);
245245
return result;
246246
}
247+
248+
public async Task<T> ShowPopup<T>(Popup<T> popup)
249+
{
250+
if(Inner != null){
251+
return await Inner.ShowPopup(popup);
252+
}
253+
254+
return await popup.Result; // This probably isn't right
255+
}
247256
}
248257
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
namespace Xamarin.Forms
2+
{
3+
/// <summary>
4+
/// BinaryResultPopup is a convenience class for quickly creating popups which require a
5+
/// binary (e.g. yes/no, accept/reject, tastes great/less filling) response from a user.
6+
/// It takes whatever content the creator passes in and adds buttons for an affirmative
7+
/// and negative response to the bottom.
8+
/// The creator can specify the text for the affirmative/negative buttons.
9+
/// </summary>
10+
public class BinaryResultPopup : Popup<BinaryResult>
11+
{
12+
public BinaryResultPopup(View content, string title = null, bool isLightDismissEnabled = true, View anchor = null, Size size = new Size(),
13+
string affirmativeText = "OK", string negativeText = null)
14+
: base(content, title, isLightDismissEnabled, anchor, size)
15+
{
16+
var view = new ContentView();
17+
18+
// Set up the grid in which to display the user's content and our affirmative/negative buttons
19+
var layout = new Grid { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill };
20+
21+
layout.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
22+
layout.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
23+
24+
layout.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
25+
layout.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
26+
27+
layout.Children.Add(content);
28+
Grid.SetRow(content, 0);
29+
Grid.SetColumnSpan(content, 2);
30+
31+
// Create the affirmative button and add it to the layout
32+
var affirmativeButton = new Button { Text = affirmativeText, HorizontalOptions = LayoutOptions.Center };
33+
affirmativeButton.Clicked += (sender, args) => Dismiss(BinaryResult.Affirmative);
34+
layout.Children.Add(affirmativeButton);
35+
Grid.SetRow(affirmativeButton, 1);
36+
37+
38+
if (!string.IsNullOrEmpty(negativeText))
39+
{
40+
// If there's going to be a negative button, create it and add it to the bottom row, second column
41+
var negativeButton = new Button { Text = negativeText, HorizontalOptions = LayoutOptions.Center };
42+
negativeButton.Clicked += (sender, args) => Dismiss(BinaryResult.Negative);
43+
layout.Children.Add(negativeButton);
44+
Grid.SetRow(negativeButton, 1);
45+
Grid.SetColumn(negativeButton, 1);
46+
}
47+
else
48+
{
49+
// If there's no negative button, make the affirmative button span the whole row
50+
Grid.SetColumnSpan(affirmativeButton, 2);
51+
}
52+
53+
view.Content = layout;
54+
55+
// Set this popup's view to the layout we just created
56+
View = view;
57+
}
58+
59+
protected override BinaryResult OnLightDismissed()
60+
{
61+
return BinaryResult.Negative;
62+
}
63+
}
64+
65+
public enum BinaryResult
66+
{
67+
Negative,
68+
Affirmative
69+
}
70+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace Xamarin.Forms
4+
{
5+
/// <summary>
6+
/// Specifies a View to be used as popup content for popups which return a result
7+
/// </summary>
8+
/// <typeparam name="T">The type of result returned by the popup</typeparam>
9+
public interface IPopupView<out T>
10+
{
11+
View View { get; }
12+
13+
void SetDismissDelegate(Action<T> dismissDelegate);
14+
}
15+
}

0 commit comments

Comments
 (0)