Простое Live Activity отображается правильно, но когда я переключился на более подробный макет Live Activity, на экране блокировки ничего не появляется, или Dynamic Island не вызывает ошибок от Flutter.
Рабочий код:
MineAppWidgetLiveActivity.swift
import ActivityKit
import WidgetKit
import SwiftUI
// 1. ATTRIBUTES
struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
public struct ContentState: Codable, Hashable {
var score: String
}
var matchName: String
}
extension LiveActivitiesAppAttributes {
func prefixedKey(_ key: String) -> String {
"\(id)_\(key)"
}
}
let sharedDefault = UserDefaults(suiteName: "group.com.example.appLiveActivitytest")!
struct MineAppWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in
VStack {
Text("
.font(.headline)
Text(context.state.score)
.font(.title)
}
.padding()
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.center) {
Text("Score: \(context.state.score)")
}
} compactLeading: {
Text("
} compactTrailing: {
Text(context.state.score)
} minimal: {
Text("
}
}
}
}
MineAppWidgetBundle.swift
import WidgetKit
import SwiftUI
@main
struct MineAppWidgetBundle: WidgetBundle {
var body: some Widget {
MineAppWidget()
MineAppWidgetLiveActivity()
}
}
main.dart
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
final _liveActivitiesPlugin = LiveActivities();
String? _latestActivityId;
@override
void initState() {
_liveActivitiesPlugin.init(
appGroupId: 'group.com.example.appLiveActivitytest',
requestAndroidNotificationPermission: true,
);
super.initState();
}
Future start() async {
_latestActivityId = await _liveActivitiesPlugin.createActivity(
DateTime.now().millisecondsSinceEpoch.toString(),
{"score": "0 - 0"},
);
setState(() {});
}
Future update() async {
if (_latestActivityId == null) return;
final asd = await _liveActivitiesPlugin.updateActivity(_latestActivityId!, {
"score": "5 - 3",
});
log(asd.toString());
}
Future stop() async {
if (_latestActivityId == null) return;
_liveActivitiesPlugin.endAllActivities();
_latestActivityId = null;
setState(() {});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text("Live Activities Test")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: start, child: const Text("Start")),
ElevatedButton(onPressed: update, child: const Text("Update")),
ElevatedButton(onPressed: stop, child: const Text("Stop")),
Text("Activity: ${_latestActivityId ?? 'None'}"),
],
),
),
),
);
}
}
код ниже не работает
main.dart
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
final _liveActivitiesPlugin = LiveActivities();
String? _latestActivityId;
Timer? _timer;
double _currentProgress = 0.0;
@override
void initState() {
_liveActivitiesPlugin.init(
appGroupId: 'group.com.example.appLiveActivitytest',
requestAndroidNotificationPermission: true,
);
super.initState();
}
Future start() async {
try {
_currentProgress = 0.0;
final activityData = {
"status": "Preparing your order...",
"driverName": "Assigning...",
"estimatedTime": "1:00 PM",
"progress": 0.0,
};
_latestActivityId = await _liveActivitiesPlugin.createActivity(
"Burger King",
activityData,
);
setState(() {});
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 3), (timer) async {
if (_latestActivityId == null) {
timer.cancel();
return;
}
_currentProgress += 0.1;
if (_currentProgress >= 1.0) _currentProgress = 1.0;
final status = _currentProgress >= 1.0
? "Enjoy your meal!"
: _currentProgress >= 0.8
? "Arriving soon"
: _currentProgress >= 0.5
? "Driver picked up order"
: "Preparing your order...";
final updateData = {
"status": status,
"driverName": "Mike",
"estimatedTime": "12:55 PM",
"progress": _currentProgress,
};
await _liveActivitiesPlugin.updateActivity(
_latestActivityId!,
updateData,
);
log("Progress: $_currentProgress");
if (_currentProgress >= 1.0) {
stop();
timer.cancel();
}
});
} catch (e) {
log(e.toString());
}
}
Future stop() async {
_timer?.cancel();
if (_latestActivityId == null) return;
await _liveActivitiesPlugin.endAllActivities();
_latestActivityId = null;
_currentProgress = 0.0;
setState(() {});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text("Delivery Tracker")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_latestActivityId != null)
const Text(
"Order Active!",
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
ElevatedButton.icon(
icon: const Icon(Icons.restaurant),
label: const Text("Place Order (Start)"),
onPressed: start,
),
const SizedBox(height: 10),
ElevatedButton.icon(
icon: const Icon(Icons.stop),
label: const Text("Delivered (Stop)"),
onPressed: stop,
),
],
),
),
),
);
}
}
MineAppWidgetBundle.swift
import WidgetKit
import SwiftUI
@main
struct MineAppWidgetBundle: WidgetBundle {
var body: some Widget {
MineAppWidget()
MineAppWidgetLiveActivity()
}
}
MineAppWidgetLiveActivity.swift
import ActivityKit
import WidgetKit
import SwiftUI
struct DeliveryAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var status: String
var driverName: String
var estimatedTime: String
var progress: Double
}
var restaurantName: String
}
struct MineAppWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryAttributes.self) { context in
VStack(alignment: .leading) {
HStack {
Image(systemName: "fork.knife.circle.fill")
.foregroundColor(.orange)
.font(.title)
VStack(alignment: .leading) {
Text(context.attributes.restaurantName)
.font(.headline)
.foregroundColor(.white)
Text(context.state.status)
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
Text(context.state.estimatedTime)
.font(.title3)
.bold()
.foregroundColor(.white)
}
.padding(.bottom, 8)
ProgressView(value: context.state.progress, total: 1.0)
.tint(.orange)
.padding(.vertical, 4)
Text("Driver: \(context.state.driverName)")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.activityBackgroundTint(Color.black.opacity(0.85))
.activitySystemActionForegroundColor(.white)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Label(context.state.driverName, systemImage: "car.fill")
.foregroundColor(.orange)
}
DynamicIslandExpandedRegion(.trailing) {
Text(context.state.estimatedTime)
.font(.headline)
}
DynamicIslandExpandedRegion(.center) {
Text(context.attributes.restaurantName)
.font(.headline)
}
DynamicIslandExpandedRegion(.bottom) {
VStack {
ProgressView(value: context.state.progress, total: 1.0)
.tint(.orange)
Text(context.state.status)
.font(.caption)
.foregroundColor(.secondary)
}
}
} compactLeading: {
Image(systemName: "fork.knife")
} compactTrailing: {
Text(context.state.estimatedTime)
.font(.caption)
.bold()
} minimal: {
Image(systemName: "fork.knife")
}
}
}
}
Подробнее здесь: https://stackoverflow.com/questions/798 ... mic-island
Мобильная версия