-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathAppirater.cs
465 lines (402 loc) · 15.5 KB
/
Appirater.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
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
using System;
using System.Diagnostics;
using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.UIKit;
public class AppiraterSettings
{
/*
Place your Apple generated software id here.
*/
public int AppId;
/*
Your app's name.
*/
public string AppName;
/*
This is the message your users will see once they've passed the day+launches
threshold.
*/
public string Message;
/*
This is the title of the message alert that users will see.
*/
public string MessageTitle;
/*
The text of the button that rejects reviewing the app.
*/
public string CancelButton;
/*
Text of button that will send user to app review page.
*/
public string RateButton;
/*
Text for button to remind the user to review later.
*/
public string RateLaterButton;
/*
Users will need to have the same version of your app installed for this many
days before they will be prompted to rate it.
*/
public int DaysUntilPrompt;
/*
An example of a 'use' would be if the user launched the app. Bringing the app
into the foreground (on devices that support it) would also be considered
a 'use'. You tell Appirater about these events using the two methods:
[Appirater appLaunched:]
[Appirater appEnteredForeground:]
Users need to 'use' the same version of the app this many times before
before they will be prompted to rate it.
*/
public int UsesUntiPrompt;
/*
A significant event can be anything you want to be in your app. In a
telephone app, a significant event might be placing or receiving a call.
In a game, it might be beating a level or a boss. This is just another
layer of filtering that can be used to make sure that only the most
loyal of your users are being prompted to rate you on the app store.
If you leave this at a value of -1, then this won't be a criteria
used for rating. To tell Appirater that the user has performed
a significant event, call the method:
[Appirater userDidSignificantEvent:];
*/
public int SigEventsUntilPrompt;
/*
Once the rating alert is presented to the user, they might select
'Remind me later'. This value specifies how long (in days) Appirater
will wait before reminding them.
*/
public int TimeBeforeReminding;
/*
'YES' will show the Appirater alert everytime. Useful for testing how your message
looks and making sure the link to your app's review page works.
*/
public bool Debug;
public AppiraterSettings (int appId)
: this (appId, (NSString) NSBundle.MainBundle.InfoDictionary.ObjectForKey (new NSString ("CFBundleName")), false)
{
}
public AppiraterSettings (int appId, bool debug)
: this (appId, (NSString) NSBundle.MainBundle.InfoDictionary.ObjectForKey (new NSString ("CFBundleName")), debug)
{
}
public AppiraterSettings (int appId, string appName, bool debug)
{
AppId = appId;
AppName = appName;
Message = string.Format ("If you enjoy using {0}, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!", AppName);
MessageTitle = string.Format ("Rate {0}", AppName);
CancelButton = "No, Thanks";
RateButton = string.Format ("Rate {0}", AppName);
RateLaterButton = "Remind me later";
DaysUntilPrompt = 30;
UsesUntiPrompt = 20;
SigEventsUntilPrompt = -1;
TimeBeforeReminding = 1;
Debug = debug;
}
}
public class Appirater : NSObject
{
const string SELECTOR_INCREMENT_AND_RATE = "incrementAndRate:";
const string SELECTOR_INCREMENT_EVENT_AND_RATE = "incrementSignificantEventAndRate:";
const string FIRST_USE_DATE = "kAppiraterFirstUseDate";
const string USE_COUNT = "kAppiraterUseCount";
const string SIGNIFICANT_EVENT_COUNT = "kAppiraterSignificantEventCount";
const string CURRENT_VERSION = "kAppiraterCurrentVersion";
const string RATED_CURRENT_VERSION = "kAppiraterRatedCurrentVersion";
const string DECLINED_TO_RATE = "kAppiraterDeclinedToRate";
const string REMINDER_REQUEST_DATE = "kAppiraterReminderRequestDate";
const string TEMPLATE_REVIEW_URL = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id={0}";
const string TEMPLATE_REVIEW_URL_IOS7 = @"itms-apps://itunes.apple.com/{0}/app/id{1}";
readonly AppiraterSettings settings;
UIAlertView ratingAlert;
public Appirater (int appId)
: this (new AppiraterSettings (appId))
{
}
public Appirater (int appId, bool debug)
: this (new AppiraterSettings (appId, debug))
{
}
public Appirater (AppiraterSettings settings)
{
this.settings = settings;
NSNotificationCenter.DefaultCenter.AddObserver (UIApplication.WillResignActiveNotification, OnAppWillResignActive);
}
public UIAlertView RatingAlert { get { return ratingAlert; } }
/*
* Returns current app version
*/
public string CurrentVersion {
get {
return (NSString) NSBundle.MainBundle.InfoDictionary.ObjectForKey (new NSString ("CFBundleVersion"));
}
}
/*
DEPRECATED: While still functional, it's better to use
appLaunched:(BOOL)canPromptForRating instead.
Calls [Appirater appLaunched:YES]. See appLaunched: for details of functionality.
*/
public void AppLaunched ()
{
AppLaunched (true);
}
/*
Tells Appirater that the app has launched, and on devices that do NOT
support multitasking, the 'uses' count will be incremented. You should
call this method at the end of your application delegate's
application:didFinishLaunchingWithOptions: method.
If the app has been used enough to be rated (and enough significant events),
you can suppress the rating alert
by passing NO for canPromptForRating. The rating alert will simply be postponed
until it is called again with YES for canPromptForRating. The rating alert
can also be triggered by appEnteredForeground: and userDidSignificantEvent:
(as long as you pass YES for canPromptForRating in those methods).
*/
public void AppLaunched (bool canPromptForRating)
{
NSThread t = new NSThread (this, new Selector (SELECTOR_INCREMENT_AND_RATE), NSNumber.FromBoolean (canPromptForRating));
t.Start ();
}
/*
Tells Appirater that the app was brought to the foreground on multitasking
devices. You should call this method from the application delegate's
applicationWillEnterForeground: method.
If the app has been used enough to be rated (and enough significant events),
you can suppress the rating alert
by passing NO for canPromptForRating. The rating alert will simply be postponed
until it is called again with YES for canPromptForRating. The rating alert
can also be triggered by appLaunched: and userDidSignificantEvent:
(as long as you pass YES for canPromptForRating in those methods).
*/
public void AppEnteredForeground (bool canPromptForRating)
{
NSThread t = new NSThread (this, new Selector (SELECTOR_INCREMENT_AND_RATE), NSNumber.FromBoolean (canPromptForRating));
t.Start ();
}
/*
Tells Appirater that the user performed a significant event. A significant
event is whatever you want it to be. If you're app is used to make VoIP
calls, then you might want to call this method whenever the user places
a call. If it's a game, you might want to call this whenever the user
beats a level boss.
If the user has performed enough significant events and used the app enough,
you can suppress the rating alert by passing NO for canPromptForRating. The
rating alert will simply be postponed until it is called again with YES for
canPromptForRating. The rating alert can also be triggered by appLaunched:
and appEnteredForeground: (as long as you pass YES for canPromptForRating
in those methods).
*/
public void UserDidSignificantEvent (bool canPromptForRating)
{
NSThread t = new NSThread (this, new Selector (SELECTOR_INCREMENT_EVENT_AND_RATE), NSNumber.FromBoolean (canPromptForRating));
t.Start ();
}
/*
Tells Appirater to open the App Store page where the user can specify a
rating for the app. Also records the fact that this has happened, so the
user won't be prompted again to rate the app.
The only case where you should call this directly is if your app has an
explicit "Rate this app" command somewhere. In all other cases, don't worry
about calling this -- instead, just call the other functions listed above,
and let Appirater handle the bookkeeping of deciding when to ask the user
whether to rate the app.
*/
public void RateApp ()
{
if (Runtime.Arch == Arch.SIMULATOR)
Debug.WriteLine ("APPIRATER NOTE: iTunes App Store is not supported on the iOS simulator. Unable to open App Store page.");
else {
NSUserDefaults userDefaults = NSUserDefaults.StandardUserDefaults;
string reviewURL = string.Format (TEMPLATE_REVIEW_URL, settings.AppId);
int systemMajorVersion = Convert.ToInt16 (UIDevice.CurrentDevice.SystemVersion.Split ('.') [0].ToString ());
if (systemMajorVersion >= 7)
{
reviewURL = string.Format (TEMPLATE_REVIEW_URL_IOS7, NSLocale.PreferredLanguages [0], settings.AppId);
}
userDefaults.SetBool (true, RATED_CURRENT_VERSION);
userDefaults.Synchronize ();
UIApplication.SharedApplication.OpenUrl (NSUrl.FromString (reviewURL));
}
}
/*
* Restarts tracking
*/
public void Restart ()
{
string version = (NSString) NSBundle.MainBundle.InfoDictionary.ObjectForKey (new NSString ("CFBundleVersion"));
NSUserDefaults userDefaults = NSUserDefaults.StandardUserDefaults;
userDefaults.SetString (version, CURRENT_VERSION);
userDefaults.SetDouble (DateTime.Now.ToOADate (), FIRST_USE_DATE);
userDefaults.SetInt (1, USE_COUNT);
userDefaults.SetInt (0, SIGNIFICANT_EVENT_COUNT);
userDefaults.SetBool (false, RATED_CURRENT_VERSION);
userDefaults.SetBool (false, DECLINED_TO_RATE);
userDefaults.SetDouble (0, REMINDER_REQUEST_DATE);
}
void OnAppWillResignActive (NSNotification n)
{
if (settings.Debug)
Debug.WriteLine ("APPIRATER appWillResignActive");
HideRatingAlert ();
}
bool ConnectedToNetwork ()
{
return Reachability.InternetConnectionStatus () != NetworkStatus.NotReachable;
}
void ShowRatingAlert ()
{
UIAlertView alertView = new UIAlertView (settings.MessageTitle, settings.Message,
new Handler (this), settings.CancelButton, settings.RateButton,
settings.RateLaterButton);
ratingAlert = alertView;
alertView.Show ();
}
void HideRatingAlert ()
{
if (ratingAlert != null && ratingAlert.Visible) {
if (settings.Debug)
Debug.WriteLine ("APPIRATER Hiding Alert");
ratingAlert.DismissWithClickedButtonIndex (-1, false);
}
}
bool RatingConditionsHaveBeenMet ()
{
if (settings.Debug)
return true;
NSUserDefaults userDefaults = NSUserDefaults.StandardUserDefaults;
DateTime dateOfFirstLaunch = DateTime.FromOADate (userDefaults.DoubleForKey (FIRST_USE_DATE));
TimeSpan timeSinceFirstLaunch = DateTime.Now.Subtract (dateOfFirstLaunch);
TimeSpan timeUntilRate = new TimeSpan (settings.DaysUntilPrompt, 0, 0, 0);
if (timeSinceFirstLaunch < timeUntilRate)
return false;
// check if the app has been used enough
int useCount = userDefaults.IntForKey (USE_COUNT);
if (useCount < settings.UsesUntiPrompt)
return false;
// check if the user has done enough significant events
int sigEventCount = userDefaults.IntForKey (SIGNIFICANT_EVENT_COUNT);
if (sigEventCount < settings.SigEventsUntilPrompt)
return false;
// has the user previously declined to rate this version of the app?
if (userDefaults.BoolForKey (DECLINED_TO_RATE))
return false;
// has the user already rated the app?
if (userDefaults.BoolForKey (RATED_CURRENT_VERSION))
return false;
// if the user wanted to be reminded later, has enough time passed?
DateTime reminderRequestDate = DateTime.FromOADate (userDefaults.DoubleForKey (REMINDER_REQUEST_DATE));
TimeSpan timeSinceReminderRequest = DateTime.Now.Subtract (reminderRequestDate);
TimeSpan timeUntilReminder = new TimeSpan (settings.TimeBeforeReminding, 0, 0, 0);
if (timeSinceReminderRequest < timeUntilReminder)
return false;
return true;
}
void IncrementUseCount ()
{
// get the app's version
string version = CurrentVersion;
// get the version number that we've been tracking
NSUserDefaults userDefaults = NSUserDefaults.StandardUserDefaults;
string trackingVersion = userDefaults.StringForKey (CURRENT_VERSION);
if (string.IsNullOrEmpty (trackingVersion)) {
trackingVersion = version;
userDefaults.SetString (version, CURRENT_VERSION);
}
if (settings.Debug)
Debug.WriteLine ("APPIRATER Tracking version: {0}", trackingVersion);
if (trackingVersion == version) {
// check if the first use date has been set. if not, set it.
double timeInterval = userDefaults.DoubleForKey (FIRST_USE_DATE);
if (timeInterval == 0) {
timeInterval = DateTime.Now.ToOADate ();
userDefaults.SetDouble (timeInterval, FIRST_USE_DATE);
}
// increment the use count
int useCount = userDefaults.IntForKey (USE_COUNT);
useCount ++;
userDefaults.SetInt (useCount, USE_COUNT);
if (settings.Debug)
Debug.WriteLine ("APPIRATER Use count: {0}", useCount);
} else
Restart ();
userDefaults.Synchronize ();
}
void IncrementSignificantEventCount ()
{
// get the app's version
string version = CurrentVersion;
// get the version number that we've been tracking
NSUserDefaults userDefaults = NSUserDefaults.StandardUserDefaults;
string trackingVersion = userDefaults.StringForKey (CURRENT_VERSION);
if (string.IsNullOrEmpty (trackingVersion)) {
trackingVersion = version;
userDefaults.SetString (version, CURRENT_VERSION);
}
if (settings.Debug)
Debug.WriteLine ("APPIRATER Tracking version: {0}", trackingVersion);
if (trackingVersion == version) {
// check if the first use date has been set. if not, set it.
double timeInterval = userDefaults.DoubleForKey (FIRST_USE_DATE);
if (timeInterval == 0) {
timeInterval = DateTime.Now.ToOADate ();
userDefaults.SetDouble (timeInterval, FIRST_USE_DATE);
}
// increment the significant event count
int sigEventCount = userDefaults.IntForKey (SIGNIFICANT_EVENT_COUNT);
sigEventCount ++;
userDefaults.SetInt (sigEventCount, SIGNIFICANT_EVENT_COUNT);
if (settings.Debug)
Debug.WriteLine ("APPIRATER Significant event count: {0}", sigEventCount);
} else
Restart ();
userDefaults.Synchronize ();
}
[Export(SELECTOR_INCREMENT_AND_RATE)]
void IncrementAndRate (NSNumber _canPromptForRating)
{
using (new NSAutoreleasePool ()) {
IncrementUseCount ();
if (_canPromptForRating.BoolValue && RatingConditionsHaveBeenMet () && ConnectedToNetwork ())
InvokeOnMainThread (ShowRatingAlert);
}
}
[Export(SELECTOR_INCREMENT_EVENT_AND_RATE)]
void IncrementSignificantEventAndRate (NSNumber _canPromptForRating)
{
using (new NSAutoreleasePool ()) {
IncrementSignificantEventCount ();
if (_canPromptForRating.BoolValue && RatingConditionsHaveBeenMet () && ConnectedToNetwork ())
InvokeOnMainThread (ShowRatingAlert);
}
}
class Handler : UIAlertViewDelegate
{
readonly Appirater owner;
public Handler (Appirater owner)
{
this.owner = owner;
}
public override void Clicked (UIAlertView alertview, int buttonIndex)
{
NSUserDefaults userDefaults = NSUserDefaults.StandardUserDefaults;
switch (buttonIndex) {
case 0:
// they don't want to rate it
userDefaults.SetBool (true, DECLINED_TO_RATE);
userDefaults.Synchronize ();
break;
case 1:
// they want to rate it
owner.RateApp ();
break;
case 2:
// remind them later
userDefaults.SetDouble (DateTime.Now.ToOADate (), REMINDER_REQUEST_DATE);
userDefaults.Synchronize ();
break;
}
}
}
}