-
Notifications
You must be signed in to change notification settings - Fork 434
/
Copy pathUIHandler.cs
372 lines (325 loc) · 13.3 KB
/
UIHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace Firebase.Sample.Firestore {
using Firebase;
using Firebase.Extensions;
using Firebase.Firestore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using UnityEngine;
// Handler for UI buttons on the scene. Also performs some
// necessary setup (initializing the firebase app, etc) on
// startup.
public class UIHandler : MonoBehaviour {
private const int kMaxLogSize = 16382;
public GUISkin fb_GUISkin;
private Vector2 controlsScrollViewVector = Vector2.zero;
private string logText = "";
private Vector2 scrollViewVector = Vector2.zero;
protected bool UIEnabled = false;
private GUIStyle disabledButtonStyle;
// Path to the collection to query on.
protected string collectionPath = "col1";
// DocumentID within the collection. Set to empty to use an autoid (which
// obviously only works for writing new documents.)
protected string documentId = "";
protected string fieldContents;
private DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther;
protected bool isFirebaseInitialized = false;
// Currently enabled logging verbosity.
protected Firebase.LogLevel logLevel = Firebase.LogLevel.Info;
// Whether an operation is in progress.
protected bool operationInProgress;
// Cancellation token source for the current operation.
protected CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
// Previously completed task.
protected Task previousTask;
// When the app starts, check to make sure that we have
// the required dependencies to use Firebase, and if not,
// add them if possible.
protected virtual void Start() {
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => {
dependencyStatus = task.Result;
if (dependencyStatus == DependencyStatus.Available) {
InitializeFirebase();
} else {
Debug.LogError(
"Could not resolve all Firebase dependencies: " + dependencyStatus);
}
});
}
protected virtual void InitializeFirebase() {
// TODO(rgowman): Enable logging here... once the plumbing is setup to
// make this possible.
UIEnabled = true;
isFirebaseInitialized = true;
}
// Exit if escape (or back, on mobile) is pressed.
protected virtual void Update() {
if (Input.GetKeyDown(KeyCode.Escape)) {
Application.Quit();
}
}
// Output text to the debug log text field, as well as the console.
public void DebugLog(string s) {
Debug.Log(s);
logText += s + "\n";
while (logText.Length > kMaxLogSize) {
int index = logText.IndexOf("\n");
logText = logText.Substring(index + 1);
}
scrollViewVector.y = int.MaxValue;
}
// Render the log output in a scroll view.
void GUIDisplayLog() {
scrollViewVector = GUILayout.BeginScrollView(scrollViewVector);
GUILayout.Label(logText);
GUILayout.EndScrollView();
}
// Wait for task completion, throwing an exception if the task fails.
// This could be typically implemented using
// yield return new WaitUntil(() => task.IsCompleted);
// however, since many procedures in this sample nest coroutines and we want any task exceptions
// to be thrown from the top level coroutine (e.g GetKnownValue) we provide this
// CustomYieldInstruction implementation wait for a task in the context of the coroutine using
// common setup and tear down code.
class WaitForTaskCompletion : CustomYieldInstruction {
Task task;
UIHandler uiHandler;
// Create an enumerator that waits for the specified task to complete.
public WaitForTaskCompletion(UIHandler uiHandler, Task task) {
uiHandler.previousTask = task;
uiHandler.operationInProgress = true;
this.uiHandler = uiHandler;
this.task = task;
}
// Wait for the task to complete.
public override bool keepWaiting {
get {
if (task.IsCompleted) {
uiHandler.operationInProgress = false;
uiHandler.cancellationTokenSource = new CancellationTokenSource();
if (task.IsFaulted) {
string s = task.Exception.ToString();
uiHandler.DebugLog(s);
}
return false;
}
return true;
}
}
}
protected FirebaseFirestore db {
get {
return FirebaseFirestore.DefaultInstance;
}
}
// Cancel the currently running operation.
protected void CancelOperation() {
if (operationInProgress && cancellationTokenSource != null) {
DebugLog("*** Cancelling operation *** ...");
cancellationTokenSource.Cancel();
cancellationTokenSource = null;
}
}
/**
* Tests a *very* basic trip through the Firestore API.
*/
protected IEnumerator GetKnownValue() {
DocumentReference doc1 = db.Collection("col1").Document("doc1");
var task = doc1.GetSnapshotAsync();
yield return new WaitForTaskCompletion(this, task);
if (!(task.IsFaulted || task.IsCanceled)) {
DocumentSnapshot snap = task.Result;
IDictionary<string, object> dict = snap.ToDictionary();
if (dict.ContainsKey("field1")) {
fieldContents = dict["field1"].ToString();
} else {
DebugLog("ERROR: Successfully retrieved col1/doc1, but it doesn't contain 'field1' key");
}
}
}
private static string DictToString(IDictionary<string, object> d) {
return "{ " + d
.Select(kv => "(" + kv.Key + ", " + kv.Value + ")")
.Aggregate("", (current, next) => current + next + ", ")
+ "}";
}
private CollectionReference GetCollectionReference() {
return db.Collection(collectionPath);
}
private DocumentReference GetDocumentReference() {
if (documentId == "") {
return GetCollectionReference().Document();
}
return GetCollectionReference().Document(documentId);
}
private IEnumerator WriteDoc(DocumentReference doc, IDictionary<string, object> data) {
Task setTask = doc.SetAsync(data);
yield return new WaitForTaskCompletion(this, setTask);
if (!(setTask.IsFaulted || setTask.IsCanceled)) {
// Update the collectionPath/documentId because:
// 1) If the documentId field was empty, this will fill it in with the autoid. This allows
// you to manually test via a trivial 'click set', 'click get'.
// 2) In the automated test, the caller might pass in an explicit docRef rather than pulling
// the value from the UI. This keeps the UI up-to-date. (Though unclear if that's useful
// for the automated tests.)
collectionPath = doc.Parent.Id;
documentId = doc.Id;
fieldContents = "Ok";
} else {
fieldContents = "Error";
}
}
private IEnumerator UpdateDoc(DocumentReference doc, IDictionary<string, object> data) {
Task updateTask = doc.UpdateAsync(data);
yield return new WaitForTaskCompletion(this, updateTask);
if (!(updateTask.IsFaulted || updateTask.IsCanceled)) {
// Update the collectionPath/documentId because:
// 1) In the automated test, the caller might pass in an explicit docRef rather than pulling
// the value from the UI. This keeps the UI up-to-date. (Though unclear if that's useful
// for the automated tests.)
collectionPath = doc.Parent.Id;
documentId = doc.Id;
fieldContents = "Ok";
} else {
fieldContents = "Error";
}
}
private IEnumerator ReadDoc(DocumentReference doc) {
Task<DocumentSnapshot> getTask = doc.GetSnapshotAsync();
yield return new WaitForTaskCompletion(this, getTask);
if (!(getTask.IsFaulted || getTask.IsCanceled)) {
DocumentSnapshot snap = getTask.Result;
// TODO(rgowman): Handle `!snap.exists()` case.
IDictionary<string, object> resultData = snap.ToDictionary();
fieldContents = "Ok: " + DictToString(resultData);
} else {
fieldContents = "Error";
}
}
// Button that can be optionally disabled.
bool Button(string buttonText, bool enabled) {
if (disabledButtonStyle == null) {
disabledButtonStyle = new GUIStyle(fb_GUISkin.button);
disabledButtonStyle.normal.textColor = Color.grey;
disabledButtonStyle.active = disabledButtonStyle.normal;
}
var style = enabled ? fb_GUISkin.button : disabledButtonStyle;
return GUILayout.Button(buttonText, style) && enabled;
}
// Render the buttons and other controls.
void GUIDisplayControls() {
if (UIEnabled) {
controlsScrollViewVector = GUILayout.BeginScrollView(controlsScrollViewVector);
GUILayout.BeginVertical();
GUILayout.Label("CollectionPath:");
collectionPath = GUILayout.TextField(collectionPath);
GUILayout.Label("DocumentId (set to empty for autoid):");
documentId = GUILayout.TextField(documentId);
GUILayout.Label("Text:");
if (fieldContents == null) {
// TODO(rgowman): Provide instructions on how to set document contents here.
fieldContents = "Sample text... (type here)";
}
fieldContents = GUILayout.TextField(fieldContents);
GUILayout.Space(10);
GUILayout.BeginVertical();
if (Button("GetKnownValue", !operationInProgress)) {
StartCoroutine(GetKnownValue());
}
if (Button("WriteDoc", !operationInProgress)) {
// TODO(rgowman): allow these values to be set by the user via the UI.
var data = new Dictionary<string, object>{
{"f1", "v1"},
{"f2", 2},
{"f3", true},
// TODO(rgowman): Add other types here too.
};
StartCoroutine(WriteDoc(GetDocumentReference(), data));
}
if (Button("UpdateDoc", !operationInProgress)) {
// TODO(rgowman): allow these values to be set by the user via the UI.
var data = new Dictionary<string, object>{
{"f1", "v1b"},
{"f4", "v4"},
// TODO(rgowman): Add other types here too.
};
StartCoroutine(UpdateDoc(GetDocumentReference(), data));
}
if (Button("ReadDoc", !operationInProgress)) {
StartCoroutine(ReadDoc(GetDocumentReference()));
}
GUILayout.EndVertical();
GUILayout.EndVertical();
GUILayout.EndScrollView();
}
}
// Render the GUI:
void OnGUI() {
GUI.skin = fb_GUISkin;
if (dependencyStatus != Firebase.DependencyStatus.Available) {
GUILayout.Label("One or more Firebase dependencies are not present.");
GUILayout.Label("Current dependency status: " + dependencyStatus.ToString());
return;
}
// TODO(rgowman): Fix sizing on desktop. Possibly using something like the following.
// Sizing in unity is a little weird; fonts that look ok on desktop
// become really small on mobile, so they need to be adjusted. But we
// don't support desktop just yet, so we'll skip this step for now.
/*
GUI.skin.textArea.fontSize = GUI.skin.textField.fontSize;
// Reduce the text size on the desktop.
if (UnityEngine.Application.platform != RuntimePlatform.Android &&
UnityEngine.Application.platform != RuntimePlatform.IPhonePlayer) {
var fontSize = GUI.skin.textArea.fontSize / 4;
GUI.skin.textArea.fontSize = fontSize;
GUI.skin.button.fontSize = fontSize;
GUI.skin.label.fontSize = fontSize;
GUI.skin.textField.fontSize = fontSize;
}
GUI.skin.textArea.stretchHeight = true;
// Calculate the height of line of text in a text area.
if (textAreaLineHeight == 0.0f) {
textAreaLineHeight = GUI.skin.textArea.CalcSize(new GUIContent("Hello World")).y;
}
*/
Rect logArea, controlArea;
if (Screen.width < Screen.height) {
// Portrait mode
controlArea = new Rect(0.0f, 0.0f, Screen.width, Screen.height * 0.5f);
logArea = new Rect(0.0f, Screen.height * 0.5f, Screen.width, Screen.height * 0.5f);
} else {
// Landscape mode
controlArea = new Rect(0.0f, 0.0f, Screen.width * 0.5f, Screen.height);
logArea = new Rect(Screen.width * 0.5f, 0.0f, Screen.width * 0.5f, Screen.height);
}
GUILayout.BeginArea(logArea);
GUIDisplayLog();
if (Button("Cancel Operation", operationInProgress)) {
CancelOperation();
}
GUILayout.EndArea();
GUILayout.BeginArea(controlArea);
GUIDisplayControls();
GUILayout.EndArea();
}
}
}