diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 00000000..89fe954e
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,18 @@
+// swift-tools-version:5.6
+import PackageDescription
+
+let package = Package(
+ name: "iRate",
+ defaultLocalization: "en",
+ platforms: [.iOS(.v13)],
+ products: [
+ .library(
+ name: "iRate",
+ targets: ["iRate"]),
+ ],
+ targets: [
+ .target(
+ name: "iRate",
+ path: "Sources/iRate")
+ ]
+)
diff --git a/Sources/iRate/Info.plist b/Sources/iRate/Info.plist
new file mode 100644
index 00000000..d3de8eef
--- /dev/null
+++ b/Sources/iRate/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/Sources/iRate/iRate.bundle/ar.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ar.lproj/Localizable.strings
new file mode 100644
index 00000000..38c59cb1
Binary files /dev/null and b/Sources/iRate/iRate.bundle/ar.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/bn.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/bn.lproj/Localizable.strings
new file mode 100644
index 00000000..a9f5977a
Binary files /dev/null and b/Sources/iRate/iRate.bundle/bn.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/bs.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/bs.lproj/Localizable.strings
new file mode 100644
index 00000000..8c7ef0d6
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/bs.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Ocijeni %@";
+"iRateAppMessage" = "Ako uživate koristeći %@, želite li odvojiti trenutak da date ocjenu? To vam neće uzeti više od minute. Hvala na vašoj podršci!";
+"iRateGameMessage" = "Ako uživate igrati %@, želite li odvojiti trenutak da date ocjenu? To vam neće uzeti više od minute. Hvala na vašoj podršci";
+"iRateCancelButton" = "Ne, hvala";
+"iRateRateButton" = "Ocijeni sada";
+"iRateRemindButton" = "Podsjeti me kasnije";
diff --git a/Sources/iRate/iRate.bundle/ca.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ca.lproj/Localizable.strings
new file mode 100644
index 00000000..746efdcc
Binary files /dev/null and b/Sources/iRate/iRate.bundle/ca.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/cs.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/cs.lproj/Localizable.strings
new file mode 100644
index 00000000..e31014f8
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/cs.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Ohodnotit %@";
+"iRateAppMessage" = "Pokud se vám líbí %@, našli byste si chvilku na ohodnocení aplikace? Nebude to trvat víc než minutu.\nDěkujeme za vaši podporu!";
+"iRateGameMessage" = "Pokud se vám %@ líbí, našli byste si chvilku na ohodnocení hry? Nebude to trvat víc než minutu. Děkujeme za vaši podporu!";
+"iRateCancelButton" = "Ne, děkuji";
+"iRateRateButton" = "Ohodnotit nyní";
+"iRateRemindButton" = "Připomenout později";
diff --git a/Sources/iRate/iRate.bundle/da.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/da.lproj/Localizable.strings
new file mode 100644
index 00000000..c4fbf791
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/da.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Vurdér %@";
+"iRateAppMessage" = "Hvis du kan lide at bruge %@, vil du så ikke bruge et øjeblik på at give en vurdering? Det tager ikke mere end et minut. Mange tak for hjælpen!";
+"iRateGameMessage" = "Hvis du kan lide at spille %@, vil du så ikke bruge et øjeblik på at vurdere det? Det tager ikke mere end et minut. Mange tak for hjælpen!";
+"iRateCancelButton" = "Nej tak";
+"iRateRateButton" = "Vurdér nu";
+"iRateRemindButton" = "Påmind mig senere";
diff --git a/Sources/iRate/iRate.bundle/de-AT.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/de-AT.lproj/Localizable.strings
new file mode 100644
index 00000000..fc060f7d
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/de-AT.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Bewerte %@";
+"iRateAppMessage" = "Wenn dir %@ gefällt, würdest Du es bitte bewerten? Dies wird nicht länger als eine Minute dauern.\nDanke für die Unterstützung!";
+"iRateGameMessage" = "Wenn dir %@ gefällt, würdest Du es bitte bewerten? Dies wird nicht länger als eine Minute dauern. Danke für die Unterstützung!";
+"iRateCancelButton" = "Nein, danke";
+"iRateRateButton" = "Jetzt bewerten";
+"iRateRemindButton" = "Später erinnern";
\ No newline at end of file
diff --git a/Sources/iRate/iRate.bundle/de.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/de.lproj/Localizable.strings
new file mode 100644
index 00000000..399e8814
Binary files /dev/null and b/Sources/iRate/iRate.bundle/de.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/el.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/el.lproj/Localizable.strings
new file mode 100644
index 00000000..502c94a1
Binary files /dev/null and b/Sources/iRate/iRate.bundle/el.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/en.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/en.lproj/Localizable.strings
new file mode 100644
index 00000000..ddaf461a
Binary files /dev/null and b/Sources/iRate/iRate.bundle/en.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/es.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/es.lproj/Localizable.strings
new file mode 100644
index 00000000..dc96e468
Binary files /dev/null and b/Sources/iRate/iRate.bundle/es.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/fa.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/fa.lproj/Localizable.strings
new file mode 100644
index 00000000..8f4c50c0
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/fa.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "امتیاز دهی به %@";
+"iRateAppMessage" = "اگر شما از برنامهی %@ لذت بردین، عالی میشه اگه با این برنامه در اپاستور امتیاز دهید! این کار بیشتر از ۱ دقیقه طول نمیکشد. با تشکر از حمایت شما!";
+"iRateGameMessage" ="اگر شما از بازی %@ لذت بردین، عالی میشه اگه با این برنامه در اپاستور امتیاز دهید! این کار بیشتر از ۱ دقیقه طول نمیکشد. با تشکر از حمایت شما!";
+"iRateCancelButton" = "نه، با تشکر";
+"iRateRateButton" = "میخوام امتیاز بدم";
+"iRateRemindButton" = "بعدا یادم بنداز";
diff --git a/Sources/iRate/iRate.bundle/fi.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/fi.lproj/Localizable.strings
new file mode 100755
index 00000000..80e1ef9c
Binary files /dev/null and b/Sources/iRate/iRate.bundle/fi.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/fr.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/fr.lproj/Localizable.strings
new file mode 100644
index 00000000..22f78aae
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/fr.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Notez %@";
+"iRateAppMessage" = "Si vous aimez utiliser %@, n'oubliez pas de donner votre avis sur l'App Store. Cela ne prend qu'une minute. Merci d'avance pour votre soutien !";
+"iRateGameMessage" = "Si vous aimez jouer à %@, n'oubliez pas de donner votre avis sur l'App Store. Cela ne prend qu'une minute. Merci d'avance pour votre soutien !";
+"iRateCancelButton" = "Non, merci";
+"iRateRateButton" = "Noter maintenant";
+"iRateRemindButton" = "Me le rappeler ultérieurement";
diff --git a/Sources/iRate/iRate.bundle/he.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/he.lproj/Localizable.strings
new file mode 100644
index 00000000..ab9d4927
Binary files /dev/null and b/Sources/iRate/iRate.bundle/he.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/hi.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/hi.lproj/Localizable.strings
new file mode 100644
index 00000000..67a532e3
Binary files /dev/null and b/Sources/iRate/iRate.bundle/hi.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/hr.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/hr.lproj/Localizable.strings
new file mode 100644
index 00000000..8c7ef0d6
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/hr.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Ocijeni %@";
+"iRateAppMessage" = "Ako uživate koristeći %@, želite li odvojiti trenutak da date ocjenu? To vam neće uzeti više od minute. Hvala na vašoj podršci!";
+"iRateGameMessage" = "Ako uživate igrati %@, želite li odvojiti trenutak da date ocjenu? To vam neće uzeti više od minute. Hvala na vašoj podršci";
+"iRateCancelButton" = "Ne, hvala";
+"iRateRateButton" = "Ocijeni sada";
+"iRateRemindButton" = "Podsjeti me kasnije";
diff --git a/Sources/iRate/iRate.bundle/hu.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/hu.lproj/Localizable.strings
new file mode 100644
index 00000000..b2e78048
Binary files /dev/null and b/Sources/iRate/iRate.bundle/hu.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/id.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/id.lproj/Localizable.strings
new file mode 100644
index 00000000..37589219
Binary files /dev/null and b/Sources/iRate/iRate.bundle/id.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/it.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/it.lproj/Localizable.strings
new file mode 100644
index 00000000..b3abcbca
Binary files /dev/null and b/Sources/iRate/iRate.bundle/it.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/ja.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ja.lproj/Localizable.strings
new file mode 100644
index 00000000..60c07105
Binary files /dev/null and b/Sources/iRate/iRate.bundle/ja.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/ko.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ko.lproj/Localizable.strings
new file mode 100755
index 00000000..93599c30
Binary files /dev/null and b/Sources/iRate/iRate.bundle/ko.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/mk.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/mk.lproj/Localizable.strings
new file mode 100644
index 00000000..6c99ce5d
Binary files /dev/null and b/Sources/iRate/iRate.bundle/mk.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/nl.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/nl.lproj/Localizable.strings
new file mode 100644
index 00000000..e4dedf9d
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/nl.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Beoordeel %@";
+"iRateAppMessage" = "Als het gebruik van %@ je bevalt, wil je dan een moment nemen om het te beoordelen? Het duurt nog geen minuut. Bedankt voor je steun!";
+"iRateGameMessage" = "Als het spelen van %@ je bevalt, wil je dan een moment nemen om het te beoordelen? Het duurt nog geen minuut. Bedankt voor je steun!";
+"iRateCancelButton" = "Nee, bedankt";
+"iRateRateButton" = "Beoordeel nu";
+"iRateRemindButton" = "Herinner me er later aan";
\ No newline at end of file
diff --git a/Sources/iRate/iRate.bundle/no.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/no.lproj/Localizable.strings
new file mode 100755
index 00000000..212909b9
Binary files /dev/null and b/Sources/iRate/iRate.bundle/no.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/pa.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/pa.lproj/Localizable.strings
new file mode 100644
index 00000000..3647d914
Binary files /dev/null and b/Sources/iRate/iRate.bundle/pa.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/pl.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/pl.lproj/Localizable.strings
new file mode 100644
index 00000000..9d4f94d9
Binary files /dev/null and b/Sources/iRate/iRate.bundle/pl.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/pt.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/pt.lproj/Localizable.strings
new file mode 100644
index 00000000..6ff1900a
Binary files /dev/null and b/Sources/iRate/iRate.bundle/pt.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/ru.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ru.lproj/Localizable.strings
new file mode 100644
index 00000000..fdd3af94
Binary files /dev/null and b/Sources/iRate/iRate.bundle/ru.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/sk.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/sk.lproj/Localizable.strings
new file mode 100644
index 00000000..84c739b5
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/sk.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Ohodnotiť %@";
+"iRateAppMessage" = "Ak sa vám páči %@, našli by ste si chvíľku na ohodnotenie aplikácie? Nebude to trvať viac ako minútu.\nĎakujeme za vašu podporu!";
+"iRateGameMessage" = "Ak sa vám hra %@ páči, našli by ste si chvíľku na ohodnotenie aplikácie? Nebude to trvať viac ako minútu. Ďakujeme za vašu podporu!";
+"iRateCancelButton" = "Nie, Ďakujem";
+"iRateRateButton" = "Ohodnotiť teraz";
+"iRateRemindButton" = "Pripomenúť neskôr";
\ No newline at end of file
diff --git a/Sources/iRate/iRate.bundle/sl.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/sl.lproj/Localizable.strings
new file mode 100755
index 00000000..25112d2e
Binary files /dev/null and b/Sources/iRate/iRate.bundle/sl.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/sv.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/sv.lproj/Localizable.strings
new file mode 100644
index 00000000..22c1fa12
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/sv.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Betygsätt %@";
+"iRateAppMessage" = "Gillar du %@ och kan tänka dig att betygsätta den? Det tar inte mer än en minut. Tack för ditt stöd!";
+"iRateGameMessage" = "Gillar du att spela %@ och kan tänka dig att betygsätta det? Det tar inte mer än en minut. Tack för ditt stöd!";
+"iRateCancelButton" = "Nej tack";
+"iRateRateButton" = "Betygsätt nu!";
+"iRateRemindButton" = "Påminn mig senare";
\ No newline at end of file
diff --git a/Sources/iRate/iRate.bundle/th.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/th.lproj/Localizable.strings
new file mode 100644
index 00000000..7486e240
Binary files /dev/null and b/Sources/iRate/iRate.bundle/th.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/tr.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/tr.lproj/Localizable.strings
new file mode 100644
index 00000000..807f9293
Binary files /dev/null and b/Sources/iRate/iRate.bundle/tr.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/uk.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/uk.lproj/Localizable.strings
new file mode 100644
index 00000000..055fe50a
Binary files /dev/null and b/Sources/iRate/iRate.bundle/uk.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/ur-IN.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ur-IN.lproj/Localizable.strings
new file mode 100644
index 00000000..b174a904
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/ur-IN.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+"iRateMessageTitle" = "کو ریٹ کیجیے %@";
+"iRateAppMessage" = "اگر آپ نے %@ کو مفید پایا ہے تو کیا آپ اپنے قیمتی وقت میں سے چند لمحے نکال کر اس کو ریٹ کریں گے؟ اس میں ایک منٹ سے زیادہ نہیں لگے گا، آپ کے تعاون کا شکریہ!";
+"iRateGameMessage" = "اگر آپ %@ کھیل کر محظوظ ہوئے ہیں تو کیا آپ اپنے قیمتی وقت میں سے چند لمحے نکال کر اس کو ریٹ کریں گے؟ اس میں ایک منٹ سے زیادہ نہیں لگے گا، آپ کے تعاون کا شکریہ!";
+"iRateCancelButton" = "نہیں، شکریہ";
+"iRateRateButton" = "ابھی ریٹ کیجیے";
+"iRateRemindButton" = "مجھے بعد میں یاد دلائیں";
+"iRateOkay" = "ٹھیک ہے";
diff --git a/Sources/iRate/iRate.bundle/ur-PK.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ur-PK.lproj/Localizable.strings
new file mode 100644
index 00000000..b174a904
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/ur-PK.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+"iRateMessageTitle" = "کو ریٹ کیجیے %@";
+"iRateAppMessage" = "اگر آپ نے %@ کو مفید پایا ہے تو کیا آپ اپنے قیمتی وقت میں سے چند لمحے نکال کر اس کو ریٹ کریں گے؟ اس میں ایک منٹ سے زیادہ نہیں لگے گا، آپ کے تعاون کا شکریہ!";
+"iRateGameMessage" = "اگر آپ %@ کھیل کر محظوظ ہوئے ہیں تو کیا آپ اپنے قیمتی وقت میں سے چند لمحے نکال کر اس کو ریٹ کریں گے؟ اس میں ایک منٹ سے زیادہ نہیں لگے گا، آپ کے تعاون کا شکریہ!";
+"iRateCancelButton" = "نہیں، شکریہ";
+"iRateRateButton" = "ابھی ریٹ کیجیے";
+"iRateRemindButton" = "مجھے بعد میں یاد دلائیں";
+"iRateOkay" = "ٹھیک ہے";
diff --git a/Sources/iRate/iRate.bundle/ur.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/ur.lproj/Localizable.strings
new file mode 100644
index 00000000..b174a904
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/ur.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+"iRateMessageTitle" = "کو ریٹ کیجیے %@";
+"iRateAppMessage" = "اگر آپ نے %@ کو مفید پایا ہے تو کیا آپ اپنے قیمتی وقت میں سے چند لمحے نکال کر اس کو ریٹ کریں گے؟ اس میں ایک منٹ سے زیادہ نہیں لگے گا، آپ کے تعاون کا شکریہ!";
+"iRateGameMessage" = "اگر آپ %@ کھیل کر محظوظ ہوئے ہیں تو کیا آپ اپنے قیمتی وقت میں سے چند لمحے نکال کر اس کو ریٹ کریں گے؟ اس میں ایک منٹ سے زیادہ نہیں لگے گا، آپ کے تعاون کا شکریہ!";
+"iRateCancelButton" = "نہیں، شکریہ";
+"iRateRateButton" = "ابھی ریٹ کیجیے";
+"iRateRemindButton" = "مجھے بعد میں یاد دلائیں";
+"iRateOkay" = "ٹھیک ہے";
diff --git a/Sources/iRate/iRate.bundle/vi.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/vi.lproj/Localizable.strings
new file mode 100644
index 00000000..05b75cc3
--- /dev/null
+++ b/Sources/iRate/iRate.bundle/vi.lproj/Localizable.strings
@@ -0,0 +1,6 @@
+"iRateMessageTitle" = "Đánh giá %@";
+"iRateAppMessage" = "Nếu thích sử dụng %@, bạn có muốn giành một chút thời gian để đánh giá nó? Sẽ không lâu hơn một phút. Cảm ơn sự hỗ trợ của bạn!";
+"iRateGameMessage" = "Nếu thích chơi %@, bạn có muốn giành một chút thời gian để đánh giá nó? Sẽ không lâu hơn một phút. Cảm ơn sự hỗ trợ của bạn!";
+"iRateCancelButton" = "Không, Cảm ơn";
+"iRateRateButton" = "Đánh Giá Ngay";
+"iRateRemindButton" = "Nhắc Tôi Sau";
diff --git a/Sources/iRate/iRate.bundle/zh-Hans.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/zh-Hans.lproj/Localizable.strings
new file mode 100644
index 00000000..0db893be
Binary files /dev/null and b/Sources/iRate/iRate.bundle/zh-Hans.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/zh-Hant.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/zh-Hant.lproj/Localizable.strings
new file mode 100644
index 00000000..923de48d
Binary files /dev/null and b/Sources/iRate/iRate.bundle/zh-Hant.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.bundle/zh-TW.lproj/Localizable.strings b/Sources/iRate/iRate.bundle/zh-TW.lproj/Localizable.strings
new file mode 100644
index 00000000..923de48d
Binary files /dev/null and b/Sources/iRate/iRate.bundle/zh-TW.lproj/Localizable.strings differ
diff --git a/Sources/iRate/iRate.h b/Sources/iRate/iRate.h
new file mode 100644
index 00000000..bb09d7b8
--- /dev/null
+++ b/Sources/iRate/iRate.h
@@ -0,0 +1,177 @@
+//
+// iRate.h
+//
+// Version 1.12.2
+//
+// Created by Nick Lockwood on 26/01/2011.
+// Copyright 2011 Charcoal Design
+//
+// Distributed under the permissive zlib license
+// Get the latest version from here:
+//
+// https://github.com/nicklockwood/iRate
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis"
+
+
+#import
+#undef weak_delegate
+#if __has_feature(objc_arc_weak) && \
+(TARGET_OS_IPHONE || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8)
+#define weak_delegate weak
+#else
+#define weak_delegate unsafe_unretained
+#endif
+
+
+#import
+#if TARGET_OS_IPHONE
+#import
+#import
+#define IRATE_EXTERN UIKIT_EXTERN
+#else
+#import
+#define IRATE_EXTERN APPKIT_EXTERN
+#endif
+
+//! Project version number for iRate.
+FOUNDATION_EXPORT double iRateVersionNumber;
+
+//! Project version string for iRate.
+FOUNDATION_EXPORT const unsigned char iRateVersionString[];
+
+IRATE_EXTERN NSUInteger const iRateAppStoreGameGenreID;
+IRATE_EXTERN NSString *const iRateErrorDomain;
+
+//localisation string keys
+IRATE_EXTERN NSString *const iRateMessageTitleKey; //iRateMessageTitle
+IRATE_EXTERN NSString *const iRateAppMessageKey; //iRateAppMessage
+IRATE_EXTERN NSString *const iRateGameMessageKey; //iRateGameMessage
+IRATE_EXTERN NSString *const iRateUpdateMessageKey; //iRateUpdateMessage
+IRATE_EXTERN NSString *const iRateCancelButtonKey; //iRateCancelButton
+IRATE_EXTERN NSString *const iRateRemindButtonKey; //iRateRemindButton
+IRATE_EXTERN NSString *const iRateRateButtonKey; //iRateRateButton
+
+//notification keys
+IRATE_EXTERN NSString *const iRateCouldNotConnectToAppStore;
+IRATE_EXTERN NSString *const iRateDidDetectAppUpdate;
+IRATE_EXTERN NSString *const iRateDidPromptForRating;
+IRATE_EXTERN NSString *const iRateUserDidAttemptToRateApp;
+IRATE_EXTERN NSString *const iRateUserDidDeclineToRateApp;
+IRATE_EXTERN NSString *const iRateUserDidRequestReminderToRateApp;
+IRATE_EXTERN NSString *const iRateDidOpenAppStore;
+
+
+typedef NS_ENUM(NSUInteger, iRateErrorCode)
+{
+ iRateErrorBundleIdDoesNotMatchAppStore = 1,
+ iRateErrorApplicationNotFoundOnAppStore,
+ iRateErrorApplicationIsNotLatestVersion,
+ iRateErrorCouldNotOpenRatingPageURL
+};
+
+
+@protocol iRateDelegate
+@optional
+
+- (void)iRateCouldNotConnectToAppStore:(NSError *)error;
+- (void)iRateDidDetectAppUpdate;
+- (BOOL)iRateShouldPromptForRating;
+- (void)iRateDidPromptForRating;
+- (void)iRateUserDidAttemptToRateApp;
+- (void)iRateUserDidDeclineToRateApp;
+- (void)iRateUserDidRequestReminderToRateApp;
+- (BOOL)iRateShouldOpenAppStore;
+- (void)iRateDidOpenAppStore;
+
+@end
+
+
+@interface iRate : NSObject
+
++ (instancetype)sharedInstance;
+
+//app store ID - this is only needed if your
+//bundle ID is not unique between iOS and Mac app stores
+@property (nonatomic, assign) NSUInteger appStoreID;
+
+//application details - these are set automatically
+@property (nonatomic, assign) NSUInteger appStoreGenreID;
+@property (nonatomic, copy) NSString *appStoreCountry;
+@property (nonatomic, copy) NSString *applicationName;
+@property (nonatomic, copy) NSString *applicationVersion;
+@property (nonatomic, copy) NSString *applicationBundleID;
+
+//usage settings - these have sensible defaults
+@property (nonatomic, assign) NSUInteger usesUntilPrompt;
+@property (nonatomic, assign) NSUInteger eventsUntilPrompt;
+@property (nonatomic, assign) float daysUntilPrompt;
+@property (nonatomic, assign) float usesPerWeekForPrompt;
+@property (nonatomic, assign) float remindPeriod;
+
+//message text, you may wish to customise these
+@property (nonatomic, copy) NSString *messageTitle;
+@property (nonatomic, copy) NSString *message;
+@property (nonatomic, copy) NSString *updateMessage;
+@property (nonatomic, copy) NSString *cancelButtonLabel;
+@property (nonatomic, copy) NSString *remindButtonLabel;
+@property (nonatomic, copy) NSString *rateButtonLabel;
+
+//debugging and prompt overrides
+
+@property (nonatomic, assign) BOOL useSKStoreReviewControllerIfAvailable;
+@property (nonatomic, assign) BOOL useUIAlertControllerIfAvailable;
+@property (nonatomic, assign) BOOL useAllAvailableLanguages;
+@property (nonatomic, assign) BOOL promptForNewVersionIfUserRated;
+@property (nonatomic, assign) BOOL onlyPromptIfLatestVersion;
+@property (nonatomic, assign) BOOL onlyPromptIfMainWindowIsAvailable;
+@property (nonatomic, assign) BOOL promptAtLaunch;
+@property (nonatomic, assign) BOOL verboseLogging;
+@property (nonatomic, assign) BOOL previewMode;
+
+//advanced properties for implementing custom behaviour
+@property (nonatomic, strong) NSURL *ratingsURL;
+@property (nonatomic, strong) NSDate *firstUsed;
+@property (nonatomic, strong) NSDate *lastReminded;
+@property (nonatomic, assign) NSUInteger usesCount;
+@property (nonatomic, assign) NSUInteger eventCount;
+@property (nonatomic, readonly) float usesPerWeek;
+@property (nonatomic, assign) BOOL declinedThisVersion;
+@property (nonatomic, readonly) BOOL declinedAnyVersion;
+@property (nonatomic, assign) BOOL ratedThisVersion;
+@property (nonatomic, readonly) BOOL ratedAnyVersion;
+@property (nonatomic, weak_delegate) id delegate;
+
+//manually control behaviour
+- (BOOL)shouldPromptForRating;
+- (void)promptForRating;
+- (void)promptIfNetworkAvailable;
+- (void)promptIfAllCriteriaMet;
+- (void)openRatingsPageInAppStore;
+- (void)logEvent:(BOOL)deferPrompt;
+- (void)remindLater;
+
+@end
+
+
+#pragma clang diagnostic pop
diff --git a/Sources/iRate/iRate.m b/Sources/iRate/iRate.m
new file mode 100644
index 00000000..83726c45
--- /dev/null
+++ b/Sources/iRate/iRate.m
@@ -0,0 +1,1266 @@
+//
+// iRate.m
+//
+// Version 1.12.2
+//
+// Created by Nick Lockwood on 26/01/2011.
+// Copyright 2011 Charcoal Design
+//
+// Distributed under the permissive zlib license
+// Get the latest version from here:
+//
+// https://github.com/nicklockwood/iRate
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+
+#import "iRate.h"
+
+
+#import
+#if !__has_feature(objc_arc)
+#error This class requires automatic reference counting
+#endif
+
+#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
+#pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis"
+#pragma clang diagnostic ignored "-Wdirect-ivar-access"
+#pragma clang diagnostic ignored "-Wpartial-availability"
+#pragma clang diagnostic ignored "-Wunused-macros"
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wformat-nonliteral"
+#pragma clang diagnostic ignored "-Wdouble-promotion"
+#pragma clang diagnostic ignored "-Wselector"
+#pragma clang diagnostic ignored "-Wgnu"
+
+
+NSUInteger const iRateAppStoreGameGenreID = 6014;
+NSString *const iRateErrorDomain = @"iRateErrorDomain";
+
+
+NSString *const iRateMessageTitleKey = @"iRateMessageTitle";
+NSString *const iRateAppMessageKey = @"iRateAppMessage";
+NSString *const iRateGameMessageKey = @"iRateGameMessage";
+NSString *const iRateUpdateMessageKey = @"iRateUpdateMessage";
+NSString *const iRateCancelButtonKey = @"iRateCancelButton";
+NSString *const iRateRemindButtonKey = @"iRateRemindButton";
+NSString *const iRateRateButtonKey = @"iRateRateButton";
+
+NSString *const iRateCouldNotConnectToAppStore = @"iRateCouldNotConnectToAppStore";
+NSString *const iRateDidDetectAppUpdate = @"iRateDidDetectAppUpdate";
+NSString *const iRateDidPromptForRating = @"iRateDidPromptForRating";
+NSString *const iRateUserDidAttemptToRateApp = @"iRateUserDidAttemptToRateApp";
+NSString *const iRateUserDidDeclineToRateApp = @"iRateUserDidDeclineToRateApp";
+NSString *const iRateUserDidRequestReminderToRateApp = @"iRateUserDidRequestReminderToRateApp";
+NSString *const iRateDidOpenAppStore = @"iRateDidOpenAppStore";
+
+static NSString *const iRateAppStoreIDKey = @"iRateAppStoreID";
+static NSString *const iRateRatedVersionKey = @"iRateRatedVersionChecked";
+static NSString *const iRateDeclinedVersionKey = @"iRateDeclinedVersion";
+static NSString *const iRateLastRemindedKey = @"iRateLastReminded";
+static NSString *const iRateLastVersionUsedKey = @"iRateLastVersionUsed";
+static NSString *const iRateFirstUsedKey = @"iRateFirstUsed";
+static NSString *const iRateUseCountKey = @"iRateUseCount";
+static NSString *const iRateEventCountKey = @"iRateEventCount";
+
+static NSString *const iRateMacAppStoreBundleID = @"com.apple.appstore";
+static NSString *const iRateAppLookupURLFormat = @"https://itunes.apple.com/%@/lookup";
+
+static NSString *const iRateiOSAppStoreURLScheme = @"itms-apps";
+static NSString *const iRateiOSAppStoreURLFormat = @"itms-apps://itunes.apple.com/app/id%@?action=write-review";
+static NSString *const iRateMacAppStoreURLFormat = @"macappstore://itunes.apple.com/app/id%@?action=write-review";
+
+
+#define SECONDS_IN_A_DAY 86400.0
+#define SECONDS_IN_A_WEEK 604800.0
+#define MAC_APP_STORE_REFRESH_DELAY 5.0
+#define REQUEST_TIMEOUT 60.0
+
+
+@interface iRate()
+
+@property (nonatomic, strong) id visibleAlert;
+@property (nonatomic, assign) BOOL checkingForPrompt;
+@property (nonatomic, assign) BOOL checkingForAppStoreID;
+
+@end
+
+
+@implementation iRate
+
++ (void)load
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self sharedInstance];
+ });
+}
+
++ (instancetype)sharedInstance
+{
+ static dispatch_once_t once;
+ static iRate *sharedInstance = nil;
+ dispatch_once(&once, ^{
+ sharedInstance = [(iRate *)[self alloc] init];
+ });
+ return sharedInstance;
+}
+
+- (NSString *)localizedStringForKey:(NSString *)key withDefault:(NSString *)defaultString
+{
+ static NSBundle *bundle = nil;
+ if (bundle == nil)
+ {
+ NSString *bundlePath = [[NSBundle bundleForClass:[iRate class]] pathForResource:@"iRate" ofType:@"bundle"];
+ if (self.useAllAvailableLanguages)
+ {
+ bundle = [NSBundle bundleWithPath:bundlePath];
+ NSString *language = [[NSLocale preferredLanguages] count]? [NSLocale preferredLanguages][0]: @"en";
+ if (![[bundle localizations] containsObject:language])
+ {
+ language = [language componentsSeparatedByString:@"-"][0];
+ }
+ if ([[bundle localizations] containsObject:language])
+ {
+ bundlePath = [bundle pathForResource:language ofType:@"lproj"];
+ }
+ }
+ bundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle];
+ }
+ defaultString = [bundle localizedStringForKey:key value:defaultString table:nil];
+ return [[NSBundle mainBundle] localizedStringForKey:key value:defaultString table:nil];
+}
+
+- (iRate *)init
+{
+ if ((self = [super init]))
+ {
+
+#if TARGET_OS_IPHONE
+
+ //register for iphone application events
+ if (&UIApplicationWillEnterForegroundNotification)
+ {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationWillEnterForeground)
+ name:UIApplicationWillEnterForegroundNotification
+ object:nil];
+ }
+
+#endif
+
+ //get country
+ self.appStoreCountry = [(NSLocale *)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
+ if ([self.appStoreCountry isEqualToString:@"150"])
+ {
+ self.appStoreCountry = @"eu";
+ }
+ else if (!self.appStoreCountry || [[self.appStoreCountry stringByReplacingOccurrencesOfString:@"[A-Za-z]{2}" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, 2)] length])
+ {
+ self.appStoreCountry = @"us";
+ }
+ else if ([self.appStoreCountry isEqualToString:@"GI"])
+ {
+ self.appStoreCountry = @"GB";
+ }
+
+ //application version (use short version preferentially)
+ self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+ if ([self.applicationVersion length] == 0)
+ {
+ self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey];
+ }
+
+ //localised application name
+ self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+ if ([self.applicationName length] == 0)
+ {
+ self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleNameKey];
+ }
+
+ //bundle id
+ self.applicationBundleID = [[NSBundle mainBundle] bundleIdentifier];
+
+ //default settings
+ self.useAllAvailableLanguages = YES;
+ self.promptForNewVersionIfUserRated = NO;
+ self.onlyPromptIfLatestVersion = YES;
+ self.onlyPromptIfMainWindowIsAvailable = YES;
+ self.promptAtLaunch = YES;
+ self.usesUntilPrompt = 10;
+ self.eventsUntilPrompt = 10;
+ self.daysUntilPrompt = 10.0;
+ self.usesPerWeekForPrompt = 0.0;
+ self.remindPeriod = 1.0;
+ self.verboseLogging = NO;
+ self.previewMode = NO;
+ self.useSKStoreReviewControllerIfAvailable = YES;
+
+#if DEBUG
+
+ //enable verbose logging in debug mode
+ self.verboseLogging = YES;
+ NSLog(@"iRate verbose logging enabled.");
+
+#endif
+
+ //app launched
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self applicationLaunched];
+ });
+ }
+ return self;
+}
+
+- (id)delegate
+{
+ if (_delegate == nil)
+ {
+
+#if TARGET_OS_IPHONE
+#define APP_CLASS UIApplication
+#else
+#define APP_CLASS NSApplication
+#endif
+
+ _delegate = (id)[(APP_CLASS *)[APP_CLASS sharedApplication] delegate];
+ }
+ return _delegate;
+}
+
+- (NSString *)messageTitle
+{
+ return [_messageTitle ?: [self localizedStringForKey:iRateMessageTitleKey withDefault:@"Rate %@"] stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
+}
+
+- (NSString *)message
+{
+ NSString *message = _message;
+ if (!message)
+ {
+ message = (self.appStoreGenreID == iRateAppStoreGameGenreID)? [self localizedStringForKey:iRateGameMessageKey withDefault:@"If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"]: [self localizedStringForKey:iRateAppMessageKey withDefault:@"If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"];
+ }
+ return [message stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
+}
+
+- (NSString *)updateMessage
+{
+ NSString *updateMessage = _updateMessage;
+ if (!updateMessage)
+ {
+ updateMessage = [self localizedStringForKey:iRateUpdateMessageKey withDefault:self.message];
+ }
+ return [updateMessage stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
+}
+
+- (NSString *)cancelButtonLabel
+{
+ return _cancelButtonLabel ?: [self localizedStringForKey:iRateCancelButtonKey withDefault:@"No, Thanks"];
+}
+
+- (NSString *)rateButtonLabel
+{
+ return _rateButtonLabel ?: [self localizedStringForKey:iRateRateButtonKey withDefault:@"Rate It Now"];
+}
+
+- (NSString *)remindButtonLabel
+{
+ return _remindButtonLabel ?: [self localizedStringForKey:iRateRemindButtonKey withDefault:@"Remind Me Later"];
+}
+
+- (NSURL *)ratingsURL
+{
+ if (_ratingsURL)
+ {
+ return _ratingsURL;
+ }
+
+ if (!self.appStoreID && self.verboseLogging)
+ {
+ NSLog(@"iRate could not find the App Store ID for this application. If the application is not intended for App Store release then you must specify a custom ratingsURL.");
+ }
+
+ NSString *URLString;
+
+#if TARGET_OS_IPHONE
+
+ URLString = iRateiOSAppStoreURLFormat;
+
+#else
+
+ URLString = iRateMacAppStoreURLFormat;
+
+#endif
+
+ return [NSURL URLWithString:[NSString stringWithFormat:URLString, @(self.appStoreID)]];
+
+}
+
+- (NSUInteger)appStoreID
+{
+ return _appStoreID ?: [[[NSUserDefaults standardUserDefaults] objectForKey:iRateAppStoreIDKey] unsignedIntegerValue];
+}
+
+- (NSDate *)firstUsed
+{
+ return [[NSUserDefaults standardUserDefaults] objectForKey:iRateFirstUsedKey];
+}
+
+- (void)setFirstUsed:(NSDate *)date
+{
+ [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateFirstUsedKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (NSDate *)lastReminded
+{
+ return [[NSUserDefaults standardUserDefaults] objectForKey:iRateLastRemindedKey];
+}
+
+- (void)setLastReminded:(NSDate *)date
+{
+ [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateLastRemindedKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (NSUInteger)usesCount
+{
+ return [[NSUserDefaults standardUserDefaults] integerForKey:iRateUseCountKey];
+}
+
+- (void)setUsesCount:(NSUInteger)count
+{
+ [[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)count forKey:iRateUseCountKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (NSUInteger)eventCount
+{
+ return [[NSUserDefaults standardUserDefaults] integerForKey:iRateEventCountKey];
+}
+
+- (void)setEventCount:(NSUInteger)count
+{
+ [[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)count forKey:iRateEventCountKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (float)usesPerWeek
+{
+ return (float)self.usesCount / ([[NSDate date] timeIntervalSinceDate:self.firstUsed] / SECONDS_IN_A_WEEK);
+}
+
+- (BOOL)declinedThisVersion
+{
+ return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] isEqualToString:self.applicationVersion];
+}
+
+- (void)setDeclinedThisVersion:(BOOL)declined
+{
+ [[NSUserDefaults standardUserDefaults] setObject:(declined? self.applicationVersion: nil) forKey:iRateDeclinedVersionKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (BOOL)declinedAnyVersion
+{
+ return [(NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] length] != 0;
+}
+
+- (BOOL)ratedVersion:(NSString *)version
+{
+ return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] isEqualToString:version];
+}
+
+- (BOOL)ratedThisVersion
+{
+ return [self ratedVersion:self.applicationVersion];
+}
+
+- (void)setRatedThisVersion:(BOOL)rated
+{
+ [[NSUserDefaults standardUserDefaults] setObject:(rated? self.applicationVersion: nil) forKey:iRateRatedVersionKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (BOOL)ratedAnyVersion
+{
+ return [(NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] length] != 0;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)incrementUseCount
+{
+ self.usesCount ++;
+}
+
+- (void)incrementEventCount
+{
+ self.eventCount ++;
+}
+
+- (BOOL)shouldPromptForRating
+{
+ //preview mode?
+ if (self.previewMode)
+ {
+ NSLog(@"iRate preview mode is enabled - make sure you disable this for release");
+ return YES;
+ }
+
+ //check if we've rated this version
+ else if (self.ratedThisVersion)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not prompt for rating because the user has already rated this version");
+ }
+ return NO;
+ }
+
+ //check if we've rated any version
+ else if (self.ratedAnyVersion && !self.promptForNewVersionIfUserRated)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not prompt for rating because the user has already rated this app, and promptForNewVersionIfUserRated is disabled");
+ }
+ return NO;
+ }
+
+ //check if we've declined to rate the app
+ else if (self.declinedAnyVersion)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not prompt for rating because the user has declined to rate the app");
+ }
+ return NO;
+ }
+
+ //check how long we've been using this version
+ else if ([[NSDate date] timeIntervalSinceDate:self.firstUsed] < self.daysUntilPrompt * SECONDS_IN_A_DAY)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not prompt for rating because the app was first used less than %g days ago", self.daysUntilPrompt);
+ }
+ return NO;
+ }
+
+ //check how many times we've used it and the number of significant events
+ else if (self.usesCount < self.usesUntilPrompt && self.eventCount < self.eventsUntilPrompt)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not prompt for rating because the app has only been used %@ times and only %@ events have been logged", @(self.usesCount), @(self.eventCount));
+ }
+ return NO;
+ }
+
+ //check if usage frequency is high enough
+ else if (self.usesPerWeek < self.usesPerWeekForPrompt)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not prompt for rating because the app has only been used %g times per week on average since it was installed", self.usesPerWeek);
+ }
+ return NO;
+ }
+
+ //check if within the reminder period
+ else if (self.lastReminded != nil && [[NSDate date] timeIntervalSinceDate:self.lastReminded] < self.remindPeriod * SECONDS_IN_A_DAY)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not prompt for rating because the user last asked to be reminded less than %g days ago", self.remindPeriod);
+ }
+ return NO;
+ }
+
+ //lets prompt!
+ return YES;
+}
+
+- (NSString *)valueForKey:(NSString *)key inJSON:(id)json
+{
+ if ([json isKindOfClass:[NSString class]])
+ {
+ //use legacy parser
+ NSRange keyRange = [json rangeOfString:[NSString stringWithFormat:@"\"%@\"", key]];
+ if (keyRange.location != NSNotFound)
+ {
+ NSInteger start = keyRange.location + keyRange.length;
+ NSRange valueStart = [json rangeOfString:@":" options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
+ if (valueStart.location != NSNotFound)
+ {
+ start = valueStart.location + 1;
+ NSRange valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
+ if (valueEnd.location != NSNotFound)
+ {
+ NSString *value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
+ value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ while ([value hasPrefix:@"\""] && ![value hasSuffix:@"\""])
+ {
+ if (valueEnd.location == NSNotFound)
+ {
+ break;
+ }
+ NSInteger newStart = valueEnd.location + 1;
+ valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(newStart, [(NSString *)json length] - newStart)];
+ value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
+ value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ }
+
+ value = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
+ value = [value stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
+ value = [value stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
+ value = [value stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
+ value = [value stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
+ value = [value stringByReplacingOccurrencesOfString:@"\\r" withString:@"\r"];
+ value = [value stringByReplacingOccurrencesOfString:@"\\t" withString:@"\t"];
+ value = [value stringByReplacingOccurrencesOfString:@"\\f" withString:@"\f"];
+ value = [value stringByReplacingOccurrencesOfString:@"\\b" withString:@"\f"];
+
+ while (YES)
+ {
+ NSRange unicode = [value rangeOfString:@"\\u"];
+ if (unicode.location == NSNotFound || unicode.location + unicode.length == 0)
+ {
+ break;
+ }
+
+ uint32_t c = 0;
+ NSString *hex = [value substringWithRange:NSMakeRange(unicode.location + 2, 4)];
+ NSScanner *scanner = [NSScanner scannerWithString:hex];
+ [scanner scanHexInt:&c];
+
+ if (c <= 0xffff)
+ {
+ value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C", (unichar)c]];
+ }
+ else
+ {
+ //convert character to surrogate pair
+ uint16_t x = (uint16_t)c;
+ uint16_t u = (c >> 16) & ((1 << 5) - 1);
+ uint16_t w = (uint16_t)u - 1;
+ unichar high = 0xd800 | (w << 6) | x >> 10;
+ unichar low = (uint16_t)(0xdc00 | (x & ((1 << 10) - 1)));
+
+ value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C%C", high, low]];
+ }
+ }
+ return value;
+ }
+ }
+ }
+ }
+ else
+ {
+ return json[key];
+ }
+ return nil;
+}
+
+- (void)setAppStoreIDOnMainThread:(NSString *)appStoreIDString
+{
+ _appStoreID = [appStoreIDString integerValue];
+ [[NSUserDefaults standardUserDefaults] setInteger:_appStoreID forKey:iRateAppStoreIDKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (void)connectionSucceeded
+{
+ if (self.checkingForAppStoreID)
+ {
+ //no longer checking
+ self.checkingForPrompt = NO;
+ self.checkingForAppStoreID = NO;
+
+ //open app store
+ [self openRatingsPageInAppStore];
+ }
+ else if (self.checkingForPrompt)
+ {
+ //no longer checking
+ self.checkingForPrompt = NO;
+
+ //confirm with delegate
+ if ([self.delegate respondsToSelector:@selector(iRateShouldPromptForRating)] && ![self.delegate iRateShouldPromptForRating])
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate did not display the rating prompt because the iRateShouldPromptForRating delegate method returned NO");
+ }
+ return;
+ }
+
+ //prompt user
+ [self promptForRating: NO];
+ }
+}
+
+- (void)connectionError:(NSError *)error
+{
+ if (self.checkingForPrompt || self.checkingForAppStoreID)
+ {
+ //no longer checking
+ self.checkingForPrompt = NO;
+ self.checkingForAppStoreID = NO;
+
+ //log the error
+ if (error)
+ {
+ NSLog(@"iRate rating process failed because: %@", [error localizedDescription]);
+ }
+ else
+ {
+ NSLog(@"iRate rating process failed because an unknown error occurred");
+ }
+
+ //could not connect
+ if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
+ {
+ [self.delegate iRateCouldNotConnectToAppStore:error];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateCouldNotConnectToAppStore
+ object:error];
+ }
+}
+
+- (void)checkForConnectivityInBackground
+{
+
+#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) \
+ || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10)
+#define IRATE_BACKGROUND_QUEUE QOS_CLASS_BACKGROUND
+#else
+#define IRATE_BACKGROUND_QUEUE DISPATCH_QUEUE_PRIORITY_BACKGROUND
+#endif
+
+ dispatch_async(dispatch_get_global_queue(IRATE_BACKGROUND_QUEUE, 0), ^{
+ [self checkForConnectivity];
+ });
+}
+
+- (void)checkForConnectivity
+{
+ @autoreleasepool
+ {
+ //first check iTunes
+ NSString *iTunesServiceURL = [NSString stringWithFormat:iRateAppLookupURLFormat, self.appStoreCountry];
+ if (_appStoreID) //important that we check ivar and not getter in case it has changed
+ {
+ iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?id=%@", @(_appStoreID)];
+ }
+ else
+ {
+ iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?bundleId=%@", self.applicationBundleID];
+ }
+
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate is checking %@ to retrieve the App Store details...", iTunesServiceURL);
+ }
+
+ NSError *error = nil;
+ NSURLResponse *response = nil;
+ NSURL *url = [NSURL URLWithString:iTunesServiceURL];
+ NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:REQUEST_TIMEOUT];
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
+#pragma clang diagnostic pop
+
+ NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
+ if (data && statusCode == 200)
+ {
+ //in case error is garbage...
+ error = nil;
+
+ id json = nil;
+ if ([NSJSONSerialization class])
+ {
+ json = [[NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)0 error:&error][@"results"] lastObject];
+ }
+ else
+ {
+ //convert to string
+ json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ }
+
+ if (!error)
+ {
+ //check bundle ID matches
+ NSString *bundleID = [self valueForKey:@"bundleId" inJSON:json];
+ if (bundleID)
+ {
+ if ([bundleID.lowercaseString isEqualToString:self.applicationBundleID.lowercaseString])
+ {
+ //get genre
+ if (self.appStoreGenreID == 0)
+ {
+ self.appStoreGenreID = [[self valueForKey:@"primaryGenreId" inJSON:json] integerValue];
+ }
+
+ //get app id
+ if (!_appStoreID)
+ {
+ NSString *appStoreIDString = [self valueForKey:@"trackId" inJSON:json];
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [self setAppStoreIDOnMainThread:appStoreIDString];
+ });
+
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate found the app on iTunes. The App Store ID is %@", appStoreIDString);
+ }
+ }
+
+ //check version
+ if (self.onlyPromptIfLatestVersion && !self.previewMode)
+ {
+ NSString *latestVersion = [self valueForKey:@"version" inJSON:json];
+ if ([latestVersion compare:self.applicationVersion options:NSNumericSearch] == NSOrderedDescending)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate found that the installed application version (%@) is not the latest version on the App Store, which is %@", self.applicationVersion, latestVersion);
+ }
+
+ error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorApplicationIsNotLatestVersion userInfo:@{NSLocalizedDescriptionKey: @"Installed app is not the latest version available"}];
+ }
+ }
+ }
+ else
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate found that the application bundle ID (%@) does not match the bundle ID of the app found on iTunes (%@) with the specified App Store ID (%@)", self.applicationBundleID, bundleID, @(self.appStoreID));
+ }
+
+ error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorBundleIdDoesNotMatchAppStore userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Application bundle ID does not match expected value of %@", bundleID]}];
+ }
+ }
+ else if (_appStoreID || !self.ratingsURL)
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate could not find this application on iTunes. If your app is not intended for App Store release then you must specify a custom ratingsURL. If this is the first release of your application then it's not a problem that it cannot be found on the store yet");
+ }
+ if (!self.previewMode)
+ {
+ error = [NSError errorWithDomain:iRateErrorDomain
+ code:iRateErrorApplicationNotFoundOnAppStore
+ userInfo:@{NSLocalizedDescriptionKey: @"The application could not be found on the App Store."}];
+ }
+ }
+ else if (!_appStoreID && self.verboseLogging)
+ {
+ NSLog(@"iRate could not find your app on iTunes. If your app is not yet on the store or is not intended for App Store release then don't worry about this");
+ }
+ }
+ }
+ else if (statusCode >= 400)
+ {
+ //http error
+ NSString *message = [NSString stringWithFormat:@"The server returned a %@ error", @(statusCode)];
+ error = [NSError errorWithDomain:@"HTTPResponseErrorDomain" code:statusCode userInfo:@{NSLocalizedDescriptionKey: message}];
+ }
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ //handle errors (ignoring sandbox issues)
+ if (error && !(error.code == EPERM && [error.domain isEqualToString:NSPOSIXErrorDomain] && self.appStoreID))
+ {
+ [self connectionError:error];
+ }
+ else if (self.appStoreID || self.previewMode)
+ {
+ //show prompt
+ [self connectionSucceeded];
+ }
+ });
+ }
+}
+
+- (void)promptIfNetworkAvailable
+{
+ if (!self.checkingForPrompt && !self.checkingForAppStoreID)
+ {
+ self.checkingForPrompt = YES;
+ [self checkForConnectivityInBackground];
+ }
+}
+
+- (void)promptIfAllCriteriaMet
+{
+ if ([self shouldPromptForRating])
+ {
+ [self promptIfNetworkAvailable];
+ }
+}
+
+- (BOOL)showRemindButton
+{
+ return [self.remindButtonLabel length];
+}
+
+- (BOOL)showCancelButton
+{
+ return [self.cancelButtonLabel length];
+}
+
+- (void)promptForRating
+{
+ [self promptForRating: YES];
+}
+
+- (void)promptForRating:(BOOL)manual
+{
+ if (!self.visibleAlert)
+ {
+ NSString *message = self.ratedAnyVersion? self.updateMessage: self.message;
+
+#if TARGET_OS_IPHONE
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_2
+
+ if (!manual && [SKStoreReviewController class])
+ {
+ [self remindLater];
+ [SKStoreReviewController requestReview];
+ }
+ else
+#endif
+ {
+ UIViewController *topController = [UIApplication sharedApplication].delegate.window.rootViewController;
+ while (topController.presentedViewController)
+ {
+ topController = topController.presentedViewController;
+ }
+
+ if ([UIAlertController class] && topController && self.useUIAlertControllerIfAvailable)
+ {
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.messageTitle message:message preferredStyle:UIAlertControllerStyleAlert];
+
+ //rate action
+ [alert addAction:[UIAlertAction actionWithTitle:self.rateButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
+ [self didDismissAlert:alert withButtonAtIndex:0];
+ }]];
+
+ //cancel action
+ if ([self showCancelButton])
+ {
+ [alert addAction:[UIAlertAction actionWithTitle:self.cancelButtonLabel style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) {
+ [self didDismissAlert:alert withButtonAtIndex:1];
+ }]];
+ }
+
+ //remind action
+ if ([self showRemindButton])
+ {
+ [alert addAction:[UIAlertAction actionWithTitle:self.remindButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
+ [self didDismissAlert:alert withButtonAtIndex:[self showCancelButton]? 2: 1];
+ }]];
+ }
+
+ self.visibleAlert = alert;
+
+ //get current view controller and present alert
+ [topController presentViewController:alert animated:YES completion:NULL];
+ }
+ else
+ {
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:self.messageTitle
+ message:message
+ delegate:(id)self
+ cancelButtonTitle:nil
+ otherButtonTitles:self.rateButtonLabel, nil];
+#pragma clang diagnostic pop
+
+ if ([self showCancelButton])
+ {
+ [alert addButtonWithTitle:self.cancelButtonLabel];
+ alert.cancelButtonIndex = 1;
+ }
+
+ if ([self showRemindButton])
+ {
+ [alert addButtonWithTitle:self.remindButtonLabel];
+ }
+
+ self.visibleAlert = alert;
+ [self.visibleAlert show];
+ }
+ }
+#else
+
+ //only show when main window is available
+ if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
+ {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self promptForRating: NO];
+ });
+ return;
+ }
+
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.messageText = self.messageTitle;
+ alert.informativeText = message;
+ [alert addButtonWithTitle:self.rateButtonLabel];
+ if ([self showCancelButton])
+ {
+ [alert addButtonWithTitle:self.cancelButtonLabel];
+ }
+ if ([self showRemindButton])
+ {
+ [alert addButtonWithTitle:self.remindButtonLabel];
+ }
+
+ self.visibleAlert = alert;
+
+#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9
+
+ if (![alert respondsToSelector:@selector(beginSheetModalForWindow:completionHandler:)])
+ {
+ [alert beginSheetModalForWindow:(__nonnull id)[NSApplication sharedApplication].mainWindow
+ modalDelegate:self
+ didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
+ contextInfo:nil];
+ }
+ else
+
+#endif
+
+ {
+ [alert beginSheetModalForWindow:(__nonnull id)[NSApplication sharedApplication].mainWindow completionHandler:^(NSModalResponse returnCode) {
+ [self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn];
+ }];
+ }
+
+#endif
+
+ //inform about prompt
+ if ([self.delegate respondsToSelector:@selector(iRateDidPromptForRating)])
+ {
+ [self.delegate iRateDidPromptForRating];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateDidPromptForRating
+ object:nil];
+ }
+}
+
+- (void)applicationLaunched
+{
+ //check if this is a new version
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *lastUsedVersion = [defaults objectForKey:iRateLastVersionUsedKey];
+ if (!self.firstUsed || ![lastUsedVersion isEqualToString:self.applicationVersion])
+ {
+ [defaults setObject:self.applicationVersion forKey:iRateLastVersionUsedKey];
+ if (!self.firstUsed || [self ratedAnyVersion])
+ {
+ //reset defaults
+ [defaults setObject:[NSDate date] forKey:iRateFirstUsedKey];
+ [defaults setInteger:0 forKey:iRateUseCountKey];
+ [defaults setInteger:0 forKey:iRateEventCountKey];
+ [defaults setObject:nil forKey:iRateLastRemindedKey];
+ [defaults synchronize];
+ }
+ else if ([[NSDate date] timeIntervalSinceDate:self.firstUsed] > (self.daysUntilPrompt - 1) * SECONDS_IN_A_DAY)
+ {
+ //if was previously installed, but we haven't yet prompted for a rating
+ //don't reset, but make sure it won't rate for a day at least
+ self.firstUsed = [[NSDate date] dateByAddingTimeInterval:(self.daysUntilPrompt - 1) * -SECONDS_IN_A_DAY];
+ }
+
+ //inform about app update
+ if ([self.delegate respondsToSelector:@selector(iRateDidDetectAppUpdate)])
+ {
+ [self.delegate iRateDidDetectAppUpdate];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateDidDetectAppUpdate
+ object:nil];
+ }
+
+ [self incrementUseCount];
+ [self checkForConnectivityInBackground];
+ if (self.promptAtLaunch)
+ {
+ [self promptIfAllCriteriaMet];
+ }
+}
+
+- (void)didDismissAlert:(__unused id)alertView withButtonAtIndex:(NSInteger)buttonIndex
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ //get button indices
+ NSInteger rateButtonIndex = 0;
+ NSInteger cancelButtonIndex = [self showCancelButton]? 1: 0;
+ NSInteger remindButtonIndex = [self showRemindButton]? cancelButtonIndex + 1: 0;
+
+ if (buttonIndex == rateButtonIndex)
+ {
+ [self rate];
+ }
+ else if (buttonIndex == cancelButtonIndex)
+ {
+ [self declineThisVersion];
+ }
+ else if (buttonIndex == remindButtonIndex)
+ {
+ [self remindLater];
+ }
+
+ //release alert
+ self.visibleAlert = nil;
+ });
+}
+
+#if TARGET_OS_IPHONE
+
+- (void)applicationWillEnterForeground
+{
+ if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
+ {
+ [self incrementUseCount];
+ [self checkForConnectivityInBackground];
+ if (self.promptAtLaunch)
+ {
+ [self promptIfAllCriteriaMet];
+ }
+ }
+}
+
+- (void)openRatingsPageInAppStore
+{
+ if (!_ratingsURL && !self.appStoreID)
+ {
+ self.checkingForAppStoreID = YES;
+ if (!self.checkingForPrompt)
+ {
+ [self checkForConnectivityInBackground];
+ }
+ return;
+ }
+
+ NSString *cantOpenMessage = nil;
+
+#if TARGET_IPHONE_SIMULATOR
+
+ if ([[self.ratingsURL scheme] isEqualToString:iRateiOSAppStoreURLScheme])
+ {
+ cantOpenMessage = @"iRate could not open the ratings page because the App Store is not available on the iOS simulator";
+ }
+
+#endif
+
+ void (^handler)(NSString *errorMessage) = ^(NSString *errorMessage)
+ {
+ if (errorMessage)
+ {
+ NSLog(@"%@", errorMessage);
+ NSError *error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorCouldNotOpenRatingPageURL userInfo:@{NSLocalizedDescriptionKey: errorMessage}];
+ if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
+ {
+ [self.delegate iRateCouldNotConnectToAppStore:error];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateCouldNotConnectToAppStore
+ object:error];
+ }
+ else
+ {
+ if ([self.delegate respondsToSelector:@selector(iRateDidOpenAppStore)])
+ {
+ [self.delegate iRateDidOpenAppStore];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateDidOpenAppStore
+ object:nil];
+ }
+ };
+
+ if (cantOpenMessage)
+ {
+ handler(cantOpenMessage);
+ }
+ else
+ {
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
+ }
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0
+
+ [[UIApplication sharedApplication] openURL:self.ratingsURL options:@{} completionHandler:^(BOOL success){
+ if (success)
+ {
+ handler(nil);
+ }
+ else
+ {
+ handler([NSString stringWithFormat:@"iRate was unable to open the specified ratings URL: %@", self.ratingsURL]);
+ }
+ }];
+#else
+ if ([[UIApplication sharedApplication] openURL:self.ratingsURL])
+ {
+ handler(nil);
+ }
+ else
+ {
+ handler([NSString stringWithFormat:@"iRate was unable to open the specified ratings URL: %@", self.ratingsURL]);
+ }
+#endif
+
+ }
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
+{
+ [self didDismissAlert:alertView withButtonAtIndex:buttonIndex];
+}
+
+#pragma clang diagnostic pop
+
+#else
+
+- (void)openAppPageWhenAppStoreLaunched
+{
+ //check if app store is running
+ for (NSRunningApplication *app in [[NSWorkspace sharedWorkspace] runningApplications])
+ {
+ if ([app.bundleIdentifier isEqualToString:iRateMacAppStoreBundleID])
+ {
+ //open app page
+ [[NSWorkspace sharedWorkspace] performSelector:@selector(openURL:) withObject:self.ratingsURL afterDelay:MAC_APP_STORE_REFRESH_DELAY];
+ return;
+ }
+ }
+
+ //try again
+ [self performSelector:@selector(openAppPageWhenAppStoreLaunched) withObject:nil afterDelay:0.0];
+}
+
+- (void)openRatingsPageInAppStore
+{
+ if (!_ratingsURL && !self.appStoreID)
+ {
+ self.checkingForAppStoreID = YES;
+ if (!self.checkingForPrompt)
+ {
+ [self checkForConnectivityInBackground];
+ }
+ return;
+ }
+
+ if (self.verboseLogging)
+ {
+ NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
+ }
+
+ [[NSWorkspace sharedWorkspace] openURL:self.ratingsURL];
+ [self openAppPageWhenAppStoreLaunched];
+ if ([self.delegate respondsToSelector:@selector(iRateDidOpenAppStore)])
+ {
+ [self.delegate iRateDidOpenAppStore];
+ }
+}
+
+- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(__unused void *)contextInfo
+{
+ [self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn];
+}
+
+#endif
+
+- (void)logEvent:(BOOL)deferPrompt
+{
+ [self incrementEventCount];
+ if (!deferPrompt)
+ {
+ [self promptIfAllCriteriaMet];
+ }
+}
+
+#pragma mark - User's actions
+
+- (void)declineThisVersion
+{
+ //ignore this version
+ self.declinedThisVersion = YES;
+
+ //log event
+ if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
+ {
+ [self.delegate iRateUserDidDeclineToRateApp];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateUserDidDeclineToRateApp
+ object:nil];
+}
+
+- (void)remindLater
+{
+ //remind later
+ self.lastReminded = [NSDate date];
+
+ //log event
+ if ([self.delegate respondsToSelector:@selector(iRateUserDidRequestReminderToRateApp)])
+ {
+ [self.delegate iRateUserDidRequestReminderToRateApp];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateUserDidRequestReminderToRateApp
+ object:nil];
+}
+
+- (void)rate
+{
+ //mark as rated
+ self.ratedThisVersion = YES;
+
+ //log event
+ if ([self.delegate respondsToSelector:@selector(iRateUserDidAttemptToRateApp)])
+ {
+ [self.delegate iRateUserDidAttemptToRateApp];
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:iRateUserDidAttemptToRateApp
+ object:nil];
+
+ // if the delegate has not implemented the method, or if it returns YES
+ if (![self.delegate respondsToSelector:@selector(iRateShouldOpenAppStore)] || [self.delegate iRateShouldOpenAppStore])
+ {
+ //launch mac app store
+ [self openRatingsPageInAppStore];
+ }
+}
+
+@end