Răsfoiți Sursa

Adding topic function to messaging test app (#5505)

Chen Liang 5 ani în urmă
părinte
comite
24e23931b4
23 a modificat fișierele cu 194 adăugiri și 43 ștergeri
  1. 9 1
      FirebaseMessaging/Apps/Sample/Sample.xcodeproj/project.pbxproj
  2. 10 0
      FirebaseMessaging/Apps/Sample/Sample/AppDelegate.swift
  3. 41 17
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json
  4. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  5. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  6. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  7. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  8. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  9. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  10. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  11. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  12. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  13. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  14. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  15. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  16. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  17. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  18. BIN
      FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
  19. 8 8
      FirebaseMessaging/Apps/Sample/Sample/Base.lproj/LaunchScreen.storyboard
  20. 33 17
      FirebaseMessaging/Apps/Sample/Sample/ContentView.swift
  21. 2 0
      FirebaseMessaging/Apps/Sample/Sample/SceneDelegate.swift
  22. 91 0
      FirebaseMessaging/Apps/Sample/Sample/TopicView.swift
  23. BIN
      FirebaseMessaging/Apps/Sample/Sample/logo.png

+ 9 - 1
FirebaseMessaging/Apps/Sample/Sample.xcodeproj/project.pbxproj

@@ -3,7 +3,7 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 50;
+	objectVersion = 51;
 	objects = {
 
 /* Begin PBXBuildFile section */
@@ -16,6 +16,8 @@
 		5125CCAC2437F472006CA5D0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5125CCAA2437F472006CA5D0 /* LaunchScreen.storyboard */; };
 		5126B5F0244F87D800C5A2CD /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5126B5EF244F87D800C5A2CD /* GoogleService-Info.plist */; };
 		513771F2243BE5DE004503C3 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513771F1243BE5DE004503C3 /* Identity.swift */; };
+		51C544FB2457A86E0093D292 /* TopicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C544FA2457A86E0093D292 /* TopicView.swift */; };
+		51C545122458E9A60093D292 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 51C545112458E9A60093D292 /* logo.png */; };
 		51D4B021244684B4001A87BB /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D4B020244684B4001A87BB /* UserSettings.swift */; };
 /* End PBXBuildFile section */
 
@@ -32,6 +34,8 @@
 		5125CCB32437F9A9006CA5D0 /* Sample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sample.entitlements; sourceTree = "<group>"; };
 		5126B5EF244F87D800C5A2CD /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
 		513771F1243BE5DE004503C3 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = "<group>"; };
+		51C544FA2457A86E0093D292 /* TopicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicView.swift; sourceTree = "<group>"; };
+		51C545112458E9A60093D292 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
 		51D4B020244684B4001A87BB /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -84,7 +88,9 @@
 				5125CCA02437F471006CA5D0 /* Sample.xcdatamodeld */,
 				513771F1243BE5DE004503C3 /* Identity.swift */,
 				51D4B020244684B4001A87BB /* UserSettings.swift */,
+				51C545112458E9A60093D292 /* logo.png */,
 				5125CCA72437F472006CA5D0 /* Preview Content */,
+				51C544FA2457A86E0093D292 /* TopicView.swift */,
 			);
 			path = Sample;
 			sourceTree = "<group>";
@@ -155,6 +161,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				51C545122458E9A60093D292 /* logo.png in Resources */,
 				5125CCAC2437F472006CA5D0 /* LaunchScreen.storyboard in Resources */,
 				5126B5F0244F87D800C5A2CD /* GoogleService-Info.plist in Resources */,
 				5125CCA92437F472006CA5D0 /* Preview Assets.xcassets in Resources */,
@@ -174,6 +181,7 @@
 				5125CC9D2437F471006CA5D0 /* AppDelegate.swift in Sources */,
 				513771F2243BE5DE004503C3 /* Identity.swift in Sources */,
 				5125CCA42437F471006CA5D0 /* ContentView.swift in Sources */,
+				51C544FB2457A86E0093D292 /* TopicView.swift in Sources */,
 				5125CC9F2437F471006CA5D0 /* SceneDelegate.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 10 - 0
FirebaseMessaging/Apps/Sample/Sample/AppDelegate.swift

@@ -23,6 +23,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     FirebaseApp.configure()
 
     let center = UNUserNotificationCenter.current()
+    center.delegate = self
+
     center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
       if error != nil {
         print("Failed requesting notification permission: ", error ?? "")
@@ -32,6 +34,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     return true
   }
 
+  // Implement this to display notification when app is in foreground.
+  func userNotificationCenter(_ center: UNUserNotificationCenter,
+                              willPresent notification: UNNotification,
+                              withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions)
+                                -> Void) {
+    completionHandler([.alert, .sound])
+  }
+
   // MARK: UISceneSession Lifecycle
 
   func application(_ application: UIApplication,

+ 41 - 17
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -1,93 +1,117 @@
 {
   "images" : [
     {
-      "idiom" : "iphone",
       "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "iphone",
       "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@3x.png",
       "scale" : "3x"
     },
     {
+      "size" : "29x29",
       "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
       "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "iphone",
       "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@3x.png",
       "scale" : "3x"
     },
     {
-      "idiom" : "iphone",
       "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "iphone",
       "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@3x.png",
       "scale" : "3x"
     },
     {
-      "idiom" : "iphone",
       "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "iphone",
       "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@3x.png",
       "scale" : "3x"
     },
     {
-      "idiom" : "ipad",
       "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@1x.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "ipad",
       "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "ipad",
       "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@1x.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "ipad",
       "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "ipad",
       "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@1x.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "ipad",
       "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "ipad",
       "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@1x.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "ipad",
       "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "ipad",
       "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-83.5x83.5@2x.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "ios-marketing",
       "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "ItunesArtwork@2x.png",
       "scale" : "1x"
     }
   ],

BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png


BIN
FirebaseMessaging/Apps/Sample/Sample/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png


+ 8 - 8
FirebaseMessaging/Apps/Sample/Sample/Base.lproj/LaunchScreen.storyboard

@@ -15,15 +15,12 @@
                         <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
-                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="📱FCM 🔔" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Eq5-E8-pqf">
-                                <rect key="frame" x="10" y="212" width="396" height="78"/>
-                                <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
-                                <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
-                                <color key="textColor" systemColor="systemTealColor" red="0.35294117650000001" green="0.7843137255" blue="0.98039215690000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
-                                <nil key="highlightedColor"/>
-                            </label>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="0wB-E8-0bF">
+                                <rect key="frame" x="20" y="106" width="382" height="213"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
+                            </imageView>
                         </subviews>
-                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <color key="backgroundColor" red="0.011764705882352941" green="0.60784313725490191" blue="0.89803921568627454" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                         <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                     </view>
                 </viewController>
@@ -32,4 +29,7 @@
             <point key="canvasLocation" x="52.173913043478265" y="375"/>
         </scene>
     </scenes>
+    <resources>
+        <image name="logo.png" width="1332" height="928"/>
+    </resources>
 </document>

+ 33 - 17
FirebaseMessaging/Apps/Sample/Sample/ContentView.swift

@@ -22,6 +22,7 @@ import FirebaseInstallations
 struct ContentView: View {
   @EnvironmentObject var identity: Identity
   @EnvironmentObject var settings: UserSettings
+  @State private var log: String = ""
 
   var body: some View {
     NavigationView {
@@ -29,25 +30,34 @@ struct ContentView: View {
       VStack {
         List {
           VStack(alignment: .leading) {
-            Text("InstanceID").font(.subheadline)
-            Text(identity.instanceID ?? "None").foregroundColor(.blue)
+            Text("InstanceID")
+              .font(.subheadline)
+              .fontWeight(.semibold)
+            Text(identity.instanceID ?? "None").foregroundColor(.green)
           }
 
           VStack(alignment: .leading) {
-            Text("Token").font(.subheadline)
+            Text("Token")
+              .font(.subheadline)
+              .fontWeight(.semibold)
             Text(identity.token ?? "None")
-              .foregroundColor(.blue)
+              .foregroundColor(.green)
               // Increase the layout priority to allow more than one line to be shown. Without this, the
               // simulator renders a single truncated line even though the Preview renders it
               // appropriately. Potentially a bug in the simulator?
               .layoutPriority(1)
           }
-
           NavigationLink(destination: SettingsView()) {
             Text("Settings")
+              .fontWeight(.semibold)
+          }
+          NavigationLink(destination: TopicView()) {
+            Text("Topic")
+              .fontWeight(.semibold)
           }
         }
         .navigationBarTitle("Firebase Messaging")
+        .foregroundColor(.blue)
 
         // MARK: Action buttons
 
@@ -58,7 +68,6 @@ struct ContentView: View {
               .fontWeight(.semibold)
           }
         }
-        .buttonStyle(IdentityButtonStyle())
 
         Button(action: deleteToken) {
           HStack {
@@ -67,7 +76,6 @@ struct ContentView: View {
               .fontWeight(.semibold)
           }
         }
-        .buttonStyle(IdentityButtonStyle())
 
         Button(action: deleteID) {
           HStack {
@@ -76,7 +84,6 @@ struct ContentView: View {
               .fontWeight(.semibold)
           }
         }
-        .buttonStyle(IdentityButtonStyle())
 
         Button(action: deleteFID) {
           HStack {
@@ -85,19 +92,22 @@ struct ContentView: View {
               .fontWeight(.semibold)
           }
         }
-        .buttonStyle(IdentityButtonStyle())
-      }
+        Text("\(log)")
+          .lineLimit(10)
+          .multilineTextAlignment(.leading)
+      }.buttonStyle(IdentityButtonStyle())
     }
   }
 
   func getToken() {
     InstanceID.instanceID().instanceID { result, error in
       guard let result = result, error == nil else {
-        print("Failed getting iid and token: ", error ?? "")
+        self.log = "Failed getting iid and token: \(String(describing: error))"
         return
       }
       self.identity.token = result.token
       self.identity.instanceID = result.instanceID
+      self.log = "Successfully got token."
     }
   }
 
@@ -108,27 +118,30 @@ struct ContentView: View {
     let senderID = app.options.gcmSenderID
     Messaging.messaging().deleteFCMToken(forSenderID: senderID) { error in
       if let error = error as NSError? {
-        print("Failed delete token: ", error)
+        self.log = "Failed deleting token: \(error)"
         return
       }
+      self.log = "Successfully deleted token."
     }
   }
 
   func deleteID() {
     InstanceID.instanceID().deleteID { error in
       if let error = error as NSError? {
-        print("Failed delete ID: ", error)
+        self.log = "Failed deleting ID: \(error)"
         return
       }
+      self.log = "Successfully deleted ID."
     }
   }
 
   func deleteFID() {
     Installations.installations().delete { error in
       if let error = error as NSError? {
-        print("Failed delete FID: ", error)
+        self.log = "Failed deleting FID: \(error)"
         return
       }
+      self.log = "Successfully deleted FID."
     }
   }
 }
@@ -142,11 +155,15 @@ struct SettingsView: View {
       List {
         Toggle(isOn: $settings.isAutoInitEnabled) {
           Text("isAutoInitEnabled")
+            .fontWeight(.semibold)
         }
         Toggle(isOn: $settings.shouldUseDelegateThanNotification) {
           Text("shouldUseDelegate")
+            .fontWeight(.semibold)
         }
       }
+      .font(.subheadline)
+      .foregroundColor(.blue)
     }
   }
 }
@@ -160,7 +177,7 @@ struct ContentView_Previews: PreviewProvider {
     // The token is a long string, generate a very long repeating string of characters to see how the view
     // will react.
     let longString = UUID().uuidString.replacingOccurrences(of: "-", with: "")
-    identity.token = Array(repeating: longString, count: 10).reduce("", +)
+    identity.token = Array(repeating: longString, count: 8).reduce("", +)
 
     return identity
   }()
@@ -185,8 +202,7 @@ struct IdentityButtonStyle: ButtonStyle {
       .frame(minWidth: 0, maxWidth: 200)
       .padding()
       .foregroundColor(.white)
-      .background(LinearGradient(gradient: Gradient(colors: [Color.blue, Color.pink]),
-                                 startPoint: .leading, endPoint: .trailing))
+      .background(Color.yellow)
       .cornerRadius(40)
       // Push the button down a bit when it's pressed.
       .scaleEffect(configuration.isPressed ? 0.9 : 1)

+ 2 - 0
FirebaseMessaging/Apps/Sample/Sample/SceneDelegate.swift

@@ -28,6 +28,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, MessagingDelegate {
   func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
              options connectionOptions: UIScene.ConnectionOptions) {
     let contentView = ContentView()
+    // Choose using delegate for token refresh.
     if settings.shouldUseDelegateThanNotification {
       Messaging.messaging().delegate = self
     }
@@ -44,6 +45,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, MessagingDelegate {
       window.makeKeyAndVisible()
     }
 
+    // Choose using notification for token refresh.
     if !settings.shouldUseDelegateThanNotification {
       // Subscribe to token refresh
       NotificationCenter.default

+ 91 - 0
FirebaseMessaging/Apps/Sample/Sample/TopicView.swift

@@ -0,0 +1,91 @@
+// Copyright 2020 Google LLC
+//
+// 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.
+
+import SwiftUI
+import FirebaseMessaging
+
+struct TopicView: View {
+  @State private var topic: String = ""
+  @State private var result: String = ""
+
+  var body: some View {
+    VStack {
+      List {
+        Text("Topic")
+          .font(.title).bold()
+          .foregroundColor(.blue)
+        TextField("Enter your topic", text: $topic)
+          .textFieldStyle(RoundedBorderTextFieldStyle())
+        Text("\(result)")
+          .lineLimit(10)
+          .multilineTextAlignment(.leading)
+      }
+
+      Button(action: subscribe) {
+        HStack {
+          Image(systemName: "t.bubble.fill").font(.body)
+          Text("Subscribe")
+            .fontWeight(.semibold)
+        }
+      }
+      Button(action: unsubscribe) {
+        HStack {
+          Image(systemName: "bin.xmark.fill").font(.body)
+          Text("Unsubscribe")
+            .fontWeight(.semibold)
+        }
+      }
+      Button(action: clear) {
+        HStack {
+          Image(systemName: "clear").font(.body)
+          Text("Clear ")
+            .fontWeight(.semibold)
+        }
+      }
+    }
+    .buttonStyle(IdentityButtonStyle())
+  }
+
+  func subscribe() {
+    Messaging.messaging().subscribe(toTopic: topic) { error in
+      if let error = error as NSError? {
+        self.result = "Failed subscription: \(error)"
+        return
+      }
+      self.result = "Successfully subscribe \(self.topic)."
+    }
+  }
+
+  func unsubscribe() {
+    Messaging.messaging().unsubscribe(fromTopic: topic) { error in
+      if let error = error as NSError? {
+        self.result = "Failed unsubscription: \(error)"
+        return
+      }
+      self.result = "Successfully unsubscribe \(self.topic)"
+    }
+  }
+
+  func clear() {
+    result = ""
+  }
+}
+
+struct TopicView_Previews: PreviewProvider {
+  static var previews: some View {
+    Group {
+      TopicView()
+    }
+  }
+}

BIN
FirebaseMessaging/Apps/Sample/Sample/logo.png