# একটি Flutter প্রজেক্টে লাইভ অ্যাক্টিভিটি iOS

আমরা একটি Flutter প্রজেক্টের জন্য স্টপওয়াচ ইমপ্লিমেন্টেশন সহ একটি লাইভ অ্যাক্টিভিটি তৈরি করব। এই প্রজেক্টে Flutter-সাইড ইমপ্লিমেন্টেশন এবং Xcode প্রজেক্টে নেটিভ ইমপ্লিমেন্টেশন উভয়ই অন্তর্ভুক্ত থাকবে।

## Flutter সাইড ইমপ্লিমেন্টেশন
আমরা একটি ক্লাস তৈরি করব যা তিনটি মেথড ইমপ্লিমেন্ট করবে:
- `startLiveActivity()`
- `updateLiveActivity()`
- `stopLiveActivity()`।

```dart
class DynamicIslandManager {
  final String channelKey;
  late final MethodChannel _methodChannel;

  DynamicIslandManager({required this.channelKey}) {
    _methodChannel = MethodChannel(channelKey);
  }

  Future<void> startLiveActivity({required Map<String, dynamic> jsonData}) async {
    try {
      await _methodChannel.invokeListMethod('startLiveActivity', jsonData);
    } catch (e, st) {
      log(e.toString(), stackTrace: st);
    }
  }

  Future<void> updateLiveActivity(
      {required Map<String, dynamic> jsonData}) async {
    try {
      await _methodChannel.invokeListMethod('updateLiveActivity', jsonData);
    } catch (e, st) {
      log(e.toString(), stackTrace: st);
    }
  }

  Future<void> stopLiveActivity() async {
    try {
      await _methodChannel.invokeListMethod('stopLiveActivity');
    } catch (e, st) {
      log(e.toString(), stackTrace: st);
    }
  }
}
```
আমরা একটি স্টপওয়াচ অ্যাপ তৈরি করব, এবং স্টপওয়াচ টাইমারের ডেটা নেটিভ মেথডে পাস করার জন্য, আমাদের একটি ডেটা মডেল তৈরি করতে হবে এবং এটিকে একটি JSON-এর মতো ম্যাপ ফরম্যাটে মেথড চ্যানেল ইনভোকেশনে পাঠাতে হবে।

```dart
class DynamicIslandStopwatchDataModel {
  final int elapsedSeconds;

  DynamicIslandStopwatchDataModel({
    required this.elapsedSeconds,
  });

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'elapsedSeconds': elapsedSeconds,
    };
  }
}
```
প্রতিটি মেথডে সংশ্লিষ্ট নেটিভ মেথড কল করার জন্য প্রয়োজনীয় কোড অন্তর্ভুক্ত থাকবে।

এটি Flutter সাইডে চালানোর জন্য প্রয়োজনীয় প্রাথমিক সেটআপ ছিল। এর পরে, প্রয়োজনীয় মেথড কল সহ একটি বেসিক স্টপওয়াচ অ্যাপ UI ইমপ্লিমেন্ট করা হয়েছিল।

```dart
class _StopWatchScreenState extends State<StopWatchScreen> {
  int seconds = 0;
  bool isRunning = false;
  Timer? timer;

  /// channel key is used to send data from flutter to swift side over
  /// a unique bridge (link between flutter & swift)
  final DynamicIslandManager diManager = DynamicIslandManager(channelKey: 'PW');

  void startTimer() {
    setState(() {
      isRunning = true;
    });

    // invoking startLiveActivity Method
    diManager.startLiveActivity(
      jsonData: DynamicIslandStopwatchDataModel(elapsedSeconds: 0).toMap(),
    );

    timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        seconds++;
      });
            // invoking the updateLiveActivity Method
      diManager.updateLiveActivity(
        jsonData: DynamicIslandStopwatchDataModel(
          elapsedSeconds: seconds,
        ).toMap(),
      );
    });
  }

  void stopTimer() {
    timer?.cancel();
    setState(() {
      seconds = 0;
      isRunning = false;
    });

    // invoking the stopLiveActivity Method
    diManager.stopLiveActivity();
  }

  @override
  void dispose() {
    timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Stopwatch App'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Stopwatch: $seconds seconds',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                ElevatedButton(
                  onPressed: isRunning ? null : startTimer,
                  child: const Text('Start'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: isRunning ? stopTimer : null,
                  child: const Text('Stop'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
```
## নেটিভ সাইড ইমপ্লিমেন্টেশন

### Xcode প্রজেক্ট কনফিগারেশন

১. Xcode-এ আপনার প্রজেক্ট খুলুন এবং General ট্যাবে মিনিমাম ডেপ্লয়মেন্ট iOS 16.1 এ সেট করুন।
<img src="/flutter-flutter-ios-live-activities-1.webp" alt="Xcode General ট্যাব যেখানে মিনিমাম ডেপ্লয়মেন্ট iOS 16.1 এ সেট করা দেখানো হচ্ছে"/>

২. `Info.plist`-এ একটি নতুন কী `NSSupportsLiveActivities` যোগ করুন এবং এর মান `YES` (বুলিয়ান) সেট করুন।

### Info.plist
```xml
<key>NSSupportsLiveActivities</key>
<true/>
```
৩. অ্যাকশন বার থেকে, **File > New > Target** নির্বাচন করুন এবং `WidgetExtensionand` অনুসন্ধান করে একটি নতুন উইজেট তৈরি করুন।

## কোড সাইড ইমপ্লিমেন্টেশন
চলুন, LiveActivityManager নামে একটি ক্লাস তৈরি করি যা আমাদের ডাইনামিক আইল্যান্ডের জন্য লাইভ অ্যাক্টিভিটিগুলো পরিচালনা করবে। এই ক্লাসে তিনটি মেথড থাকবে: `startLiveActivity()`, `updateLiveActivity()`, এবং `stopLiveActivity()`।

**Swift**

```swift
import ActivityKit
import Flutter
import Foundation

@available(iOS 16.1, *)
class LiveActivityManager {

    private var stopwatchActivity: Activity<StopwatchWidgetAttributes>? = nil

    func startLiveActivity(data: [String: Any]?, result: FlutterResult) {
        let attributes = StopwatchWidgetAttributes()

        if let info = data {
            let state = StopwatchWidgetAttributes.ContentState(
                elapsedTime: info["elapsedSeconds"] as? Int ?? 0
            )
            stopwatchActivity = try? Activity<StopwatchWidgetAttributes>.request(
                attributes: attributes, contentState: state, pushType: nil)
        } else {
            result(FlutterError(code: "418", message: "Live activity didn't invoked", details: nil))
        }
    }

    func updateLiveActivity(data: [String: Any]?, result: FlutterResult) {
        if let info = data {
            let updatedState = StopwatchWidgetAttributes.ContentState(
                elapsedTime: info["elapsedSeconds"] as? Int ?? 0
            )

            Task {
                await stopwatchActivity?.update(using: updatedState)
            }
        } else {
            result(FlutterError(code: "418", message: "Live activity didn't updated", details: nil))
        }
    }

    func stopLiveActivity(result: FlutterResult) {
        do {
            Task {
                await stopwatchActivity?.end(using: nil, dismissalPolicy: .immediate)
            }
        } catch {
            result(FlutterError(code: "418", message: error.localizedDescription, details: nil))
        }
    }
}
```

প্রতিটি মেথডের নিজস্ব নির্দিষ্ট কার্যকারিতা রয়েছে। `startLiveActivity()` (নাম থেকেই বোঝা যায়) লাইভ অ্যাক্টিভিটি শুরু করার জন্য দায়ী, যা ডাইনামিক আইল্যান্ড কার্যকারিতা ট্রিগার করে। একইভাবে, `stopLiveActivity()` এবং `updateLiveActivity()` লাইভ অ্যাক্টিভিটি বন্ধ করা এবং ডাইনামিক আইল্যান্ডে প্রদর্শিত ডেটা আপডেট করার কাজ করে।

এরপরে, `StopWatchDIWidgetLiveActivity.swift` খুলুন এবং `StopwatchDIWidgetAttributes` স্ট্রাকচারটি পরিবর্তন করুন (যেমন স্নিপেটে দেখানো হয়েছে)। এই অ্যাট্রিবিউট স্ট্রাকচারটি ডেটা মডেল হিসাবে কাজ করে যা UI-তে প্রদর্শনের জন্য তথ্য ধারণ করে (সরাসরি Flutter থেকে) এবং প্রয়োজন অনুযায়ী UI পরিবর্তনও করতে পারে।

**Swift**

```swift
struct StopwatchWidgetAttributes: ActivityAttributes {
    public typealias stopwatchStatus = ContentState

    public struct ContentState: Codable, Hashable {
        var elapsedTime: Int
    }
}
```
এখন, শুধু ডাইনামিক আইল্যান্ডের জন্য UI বাকি আছে! (দারুণ, আমরা এতদূর এসেছি!)
ডাইনামিক আইল্যান্ডের সম্পূর্ণ UI SwiftUI ব্যবহার করে তৈরি করতে হবে। এই আর্টিকেলের জন্য, একটি সাধারণ UI ডিজাইন করা হয়েছে (তবে প্রয়োজন অনুযায়ী আপনি এটি কাস্টমাইজ করতে পারেন)।

এই UI কোডটি `StopwatchWidgetLiveActivity` স্ট্রাকচারের ভিতরে লিখতে হবে। স্ট্রাকচার থেকে বিদ্যমান কোডটি সরিয়ে ফেলুন এবং আপনি নীচের কোডটি অনুসরণ করতে পারেন:

**SwiftUI**
```swift
struct StopwatchWidgetLiveActivity: Widget {

    func getTimeString(_ seconds: Int) -> String {
        let hours = seconds / 3600
        let minutes = (seconds % 3600) / 60
        let seconds = (seconds % 3600) % 60

        return hours == 0 ?
                String(format: "%02d:%02d", minutes, seconds)
                : String(format: "%02d:%02d:%02d", hours, minutes, seconds)
    }

    var body: some WidgetConfiguration {
        ActivityConfiguration(for: StopwatchWidgetAttributes.self) { context in
            HStack {
                Text("Time ellapsed")
                    .font(.system(size: 20, weight: .semibold))
                    .foregroundColor(.white)
                Spacer()
                Image(systemName: "timer")
                    .foregroundColor(.white)
                Spacer().frame(width: 10)
                Text(getTimeString(context.state.elapsedTime))
                    .font(.system(size: 24, weight: .semibold))
                    .foregroundColor(.yellow)
            }
            .padding(.horizontal)
            .activityBackgroundTint(Color.black.opacity(0.5))

        }
        dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.center) {
                    VStack(alignment: .center) {
                        Text("Pushwoosh Timer")
                        Spacer().frame(height: 24)
                        HStack {
                            Text("Time ellapsed")
                                .font(.system(size: 20, weight: .semibold))
                                .foregroundColor(.white)
                            Spacer()
                            Image(systemName: "timer")
                            Spacer().frame(width: 10)
                            Text(getTimeString(context.state.elapsedTime))
                                .font(.system(size: 24, weight: .semibold))
                                .foregroundColor(.yellow)
                        }.padding(.horizontal)
                    }
                }
            } compactLeading: {
                Image(systemName: "timer").padding(.leading, 4)
            } compactTrailing: {
                Text(getTimeString(context.state.elapsedTime)).foregroundColor(.yellow)
                    .padding(.trailing, 4)
            } minimal: {
                Image(systemName: "timer")
                    .foregroundColor(.yellow)
                    .padding(.all, 4)
            }
            .widgetURL(URL(string: "http://www.pushwoosh.com"))
            .keylineTint(Color.red)
        }
    }
}
```
`AppDelegate কোড` পরিবর্তন করতেও ভুলবেন না।

**AppDelegate.swift**
```swift
import UIKit
import Flutter

@main
@objc class AppDelegate: FlutterAppDelegate {

    private let liveActivityManager: LiveActivityManager = LiveActivityManager()

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        GeneratedPluginRegistrant.register(with: self)

        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let diChannel =  FlutterMethodChannel(name: "PW", binaryMessenger: controller.binaryMessenger)

        diChannel.setMethodCallHandler({ [weak self] (
            call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            switch call.method {
            case "startLiveActivity":
                self?.liveActivityManager.startLiveActivity(
                    data: call.arguments as? Dictionary<String,Any>,
                    result: result)
                break

            case "updateLiveActivity":
                self?.liveActivityManager.updateLiveActivity(
                    data: call.arguments as? Dictionary<String,Any>,
                    result: result)
                break

            case "stopLiveActivity":
                self?.liveActivityManager.stopLiveActivity(result: result)
                break

            default:
                result(FlutterMethodNotImplemented)
            }
        })

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```
এবং এটাই! ডাইনামিক আইল্যান্ড ইমপ্লিমেন্ট করতে এবং এটিকে Flutter কোডের সাথে লিঙ্ক করার জন্য যা যা প্রয়োজন, তার সবকিছুই এখানে কভার করা হয়েছে!

## Pushwoosh কোড ইমপ্লিমেন্টেশন
Pushwoosh Flutter প্লাগইন প্রজেক্টে লাইভ অ্যাক্টিভিটি পরিচালনা করার জন্য দুটি মেথড প্রদান করে।

```dart
  /// ডিফল্ট সেটআপ লাইভ অ্যাক্টিভিটি
  Future<void> defaultSetup() async {
    await _channel.invokeMethod("defaultSetup");
  }

  /// ডিফল্ট স্টার্ট লাইভ অ্যাক্টিভিটি
  /// [activityId] অ্যাক্টিভিটি আইডি
  /// [attributes] অ্যাট্রিবিউটস
  /// [content] কনটেন্ট
  Future<void> defaultStart(String activityId, Map<String, dynamic> attributes, Map<String, dynamic> content) async {
    await _channel.invokeMethod("defaultStart", {"activityId": activityId, "attributes": attributes, "content": content});
  }
```
`defaultSetup()` মেথডটি আপনাকে Pushwoosh সাইডে লাইভ অ্যাক্টিভিটির কাঠামো এবং টোকেন পরিচালনা করতে দেয়।

অ্যাপ্লিকেশন ইনিশিয়ালাইজেশনের সময় এই মেথডটি কল করুন।

```dart
  Pushwoosh.initialize({"app_id": "XXXXX-XXXXX"});
/**
* এই মেথডটি `defaultSetup()` কল করুন
*/
  Pushwoosh.getInstance.defaultSetup();
```
অ্যাপ ইনিশিয়ালাইজেশনের সময়, Pushwoosh আপনার পুশ-টু-স্টার্ট টোকেন সার্ভারে পাঠাবে, যা পরে আপনাকে একটি API কলের মাধ্যমে লাইভ অ্যাক্টিভিটি শুরু করার অনুমতি দেবে।
লাইভ অ্যাক্টিভিটি শুরু করার জন্য, আপনাকে একটি API রিকোয়েস্ট করতে হবে। নিচে রিকোয়েস্টের একটি উদাহরণ দেওয়া হল:

```json
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "YOUR_AUTH_API_TOKEN",
    "notifications": [
      {
        "content": "Message",
        "title":"Title",
        "live_activity": {
          "event": "start", // `start` ইভেন্ট
            "content-state": {
                "data": { // আপনাকে ডেটা কী সহ একটি ডিকশনারিতে প্যারামিটারগুলো পাস করতে হবে।
                    "emoji": "dynamic data"
                }
            },
           "attributes-type": "DefaultLiveActivityAttributes",
            "attributes": {
                "data": {
                    "name": "static data"
                }
        }
        },
        "devices": [ "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" // HWID
       ],
        "live_activity_id": "your_activity_id"
      }
    ]
  }
}
```

[API সম্পর্কে আরও বিস্তারিত ডকুমেন্টেশন](/bn/developer/api-reference/ios-live-activities-api)

আপনি যদি একটি সাধারণ লাইভ অ্যাক্টিভিটি শুরু করতে চান, উদাহরণস্বরূপ, আপনার অ্যাপ থেকে, আপনি Flutter-এ নিম্নলিখিত মেথডটি ব্যবহার করতে পারেন: `defaultStart(String activityId, Map<String, dynamic> attributes, Map<String, dynamic> content)`

এই মেথডটি যেকোনো ইভেন্টে কল করা যেতে পারে যা লাইভ অ্যাক্টিভিটি শুরু করতে ট্রিগার করে।

```dart
// লাইভ অ্যাক্টিভিটি শুরু করার ফাংশন
  void startLiveActivity() {
    // আপনার অ্যাক্টিভিটি আইডি তৈরি করুন
    String activityId = "stopwatch_activity";

    // আপনি লাইভ অ্যাক্টিভিটিতে যে অ্যাট্রিবিউটগুলো পাঠাতে চান তা নির্ধারণ করুন
    Map<String, dynamic> attributes = {
      'title': 'Stopwatch Activity',
      'description': 'This is a live activity for a stopwatch.'
    };

    // ডাইনামিক আইল্যান্ডে আপডেট করার জন্য কনটেন্ট স্টেট নির্ধারণ করুন
    Map<String, dynamic> content = {
      'elapsedSeconds': 0
    };

    // লাইভ অ্যাক্টিভিটি ট্রিগার করতে Pushwoosh-এর defaultStart মেথড কল করুন
    Pushwoosh.getInstance().defaultStart(activityId, attributes, content);
  }
```