finished schedules screens
This commit is contained in:
@@ -5,10 +5,16 @@ SnackBar errorSnackbar(
|
|||||||
Color backgroundColor = const Color.fromARGB(255, 196, 26, 14),
|
Color backgroundColor = const Color.fromARGB(255, 196, 26, 14),
|
||||||
Duration duration = const Duration(seconds: 3),
|
Duration duration = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
|
final errorText = e is String
|
||||||
|
? e
|
||||||
|
: (e.toString().contains(':')
|
||||||
|
? e.toString().substring(e.toString().indexOf(':') + 1).trim()
|
||||||
|
: e.toString());
|
||||||
|
|
||||||
return SnackBar(
|
return SnackBar(
|
||||||
backgroundColor: Color.fromARGB(255, 196, 26, 14),
|
backgroundColor: Color.fromARGB(255, 196, 26, 14),
|
||||||
content: Text(
|
content: Text(
|
||||||
e.toString().replaceFirst(RegExp(r'^[^:]+:\s*'), ''),
|
errorText,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
|
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
|
||||||
|
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
|
||||||
import 'package:blind_master/main.dart';
|
import 'package:blind_master/main.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
class DayTimePicker extends StatefulWidget {
|
class DayTimePicker extends StatefulWidget {
|
||||||
const DayTimePicker({super.key, required this.defaultTime, required this.sendSchedule});
|
const DayTimePicker({
|
||||||
|
super.key,
|
||||||
|
required this.defaultTime,
|
||||||
|
required this.sendSchedule,
|
||||||
|
required this.peripheralId,
|
||||||
|
required this.peripheralNum,
|
||||||
|
required this.deviceId,
|
||||||
|
this.existingSchedule,
|
||||||
|
this.scheduleId,
|
||||||
|
});
|
||||||
|
|
||||||
final TimeOfDay defaultTime;
|
final TimeOfDay defaultTime;
|
||||||
final void Function(TimeOfDay) sendSchedule;
|
final void Function(TimeOfDay) sendSchedule;
|
||||||
|
final int peripheralId;
|
||||||
|
final int peripheralNum;
|
||||||
|
final int deviceId;
|
||||||
|
final Map<String, dynamic>? existingSchedule;
|
||||||
|
final String? scheduleId;
|
||||||
|
|
||||||
|
bool get isEditing => existingSchedule != null && scheduleId != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DayTimePicker> createState() => _DayTimePickerState();
|
State<DayTimePicker> createState() => _DayTimePickerState();
|
||||||
}
|
}
|
||||||
@@ -16,10 +35,25 @@ class _DayTimePickerState extends State<DayTimePicker> {
|
|||||||
double _blindPosition = 0;
|
double _blindPosition = 0;
|
||||||
String imagePath = "";
|
String imagePath = "";
|
||||||
Set<DaysOfWeek> days = <DaysOfWeek>{};
|
Set<DaysOfWeek> days = <DaysOfWeek>{};
|
||||||
|
bool showError = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// If editing, pre-populate with existing schedule data
|
||||||
|
if (widget.isEditing && widget.existingSchedule != null) {
|
||||||
|
final schedule = widget.existingSchedule!;
|
||||||
|
final hour = schedule['schedule']['hours'][0] as int;
|
||||||
|
final minute = schedule['schedule']['minutes'][0] as int;
|
||||||
|
scheduleTime = TimeOfDay(hour: hour, minute: minute);
|
||||||
|
_blindPosition = (schedule['pos'] as int).toDouble();
|
||||||
|
|
||||||
|
// Pre-populate days
|
||||||
|
final daysOfWeek = schedule['schedule']['daysOfWeek'] as List;
|
||||||
|
days = daysOfWeek.map((d) => DaysOfWeek.values[d as int]).toSet();
|
||||||
|
}
|
||||||
|
|
||||||
updateBackground();
|
updateBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,30 +118,38 @@ class _DayTimePickerState extends State<DayTimePicker> {
|
|||||||
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Builder(
|
||||||
margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05),
|
builder: (context) {
|
||||||
height: MediaQuery.of(context).size.width * 0.43,
|
final containerHeight = MediaQuery.of(context).size.width * 0.43;
|
||||||
width: MediaQuery.of(context).size.width * 0.45,
|
final maxSlatHeight = containerHeight / 10;
|
||||||
child: Column(
|
final slatHeight = _blindPosition < 5
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
? maxSlatHeight * (5 - _blindPosition) / 5
|
||||||
children: List.generate(10, (index) {
|
: maxSlatHeight * (_blindPosition - 5) / 5;
|
||||||
return AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
return Container(
|
||||||
height: _blindPosition < 5 ?
|
margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05),
|
||||||
3.65 * (5 - _blindPosition)
|
height: containerHeight,
|
||||||
: 3.65 * (_blindPosition - 5),
|
width: MediaQuery.of(context).size.width * 0.45,
|
||||||
width: MediaQuery.of(context).size.width * 0.40, // example
|
child: Column(
|
||||||
color: const Color.fromARGB(255, 121, 85, 72),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
);
|
children: List.generate(10, (index) {
|
||||||
}),
|
return AnimatedContainer(
|
||||||
),
|
duration: const Duration(milliseconds: 300),
|
||||||
|
height: slatHeight,
|
||||||
|
width: MediaQuery.of(context).size.width * 0.40,
|
||||||
|
color: const Color.fromARGB(255, 121, 85, 72),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Slider on the side
|
// Slider on the side
|
||||||
Align(
|
SizedBox(
|
||||||
alignment: Alignment.centerRight,
|
width: MediaQuery.of(context).size.width * 0.10,
|
||||||
child: RotatedBox(
|
child: RotatedBox(
|
||||||
quarterTurns: -1,
|
quarterTurns: -1,
|
||||||
child: Slider(
|
child: Slider(
|
||||||
@@ -171,6 +213,7 @@ class _DayTimePickerState extends State<DayTimePicker> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
days.add(day);
|
days.add(day);
|
||||||
|
showError = false;
|
||||||
} else {
|
} else {
|
||||||
days.remove(day);
|
days.remove(day);
|
||||||
}
|
}
|
||||||
@@ -179,6 +222,18 @@ class _DayTimePickerState extends State<DayTimePicker> {
|
|||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
if (showError)
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: 10),
|
||||||
|
child: Text(
|
||||||
|
'Please select at least one day',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -196,6 +251,78 @@ class _DayTimePickerState extends State<DayTimePicker> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (days.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
showError = true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Convert DaysOfWeek enum to day numbers (0=Sunday, 1=Monday, etc.)
|
||||||
|
final daysOfWeek = days.map((day) => day.index).toList();
|
||||||
|
|
||||||
|
final timeToUse = scheduleTime ?? widget.defaultTime;
|
||||||
|
|
||||||
|
final payload = {
|
||||||
|
'periphId': widget.peripheralId,
|
||||||
|
'periphNum': widget.peripheralNum,
|
||||||
|
'deviceId': widget.deviceId,
|
||||||
|
'newPos': _blindPosition.toInt(),
|
||||||
|
'time': {
|
||||||
|
'hour': timeToUse.hour,
|
||||||
|
'minute': timeToUse.minute,
|
||||||
|
},
|
||||||
|
'daysOfWeek': daysOfWeek,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add jobId if editing
|
||||||
|
if (widget.isEditing) {
|
||||||
|
payload['jobId'] = widget.scheduleId!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final endpoint = widget.isEditing ? 'update_schedule' : 'add_schedule';
|
||||||
|
final response = await securePost(payload, endpoint);
|
||||||
|
|
||||||
|
if (response == null) throw Exception("Auth Error");
|
||||||
|
|
||||||
|
// Handle duplicate schedule (409 Conflict)
|
||||||
|
if (response.statusCode == 409) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('A schedule already exists at this time for this blind'),
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
duration: Duration(seconds: 4),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode != 201 && response.statusCode != 200) {
|
||||||
|
throw Exception("Server Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(widget.isEditing ? 'Schedule updated successfully' : 'Schedule added successfully'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(widget.isEditing ? "Update" : "Add")
|
||||||
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -307,14 +307,20 @@ class _PeripheralScreenState extends State<PeripheralScreen> {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
getImage();
|
getImage();
|
||||||
final nowUtc = DateTime.now().toUtc();
|
|
||||||
final lastSetUtc = DateTime.parse(body['last_set']);
|
if (body['last_set'] != null) {
|
||||||
final Duration difference = nowUtc.difference(lastSetUtc);
|
final nowUtc = DateTime.now().toUtc();
|
||||||
if (!lastSetUtc.isUtc) throw Exception("Why isn't the server giving UTC?");
|
final lastSetUtc = DateTime.parse(body['last_set']);
|
||||||
final diffDays = difference.inDays > 0;
|
final Duration difference = nowUtc.difference(lastSetUtc);
|
||||||
final diffHours = difference.inHours > 0;
|
if (!lastSetUtc.isUtc) throw Exception("Why isn't the server giving UTC?");
|
||||||
final diffMins = difference.inMinutes > 0;
|
final diffDays = difference.inDays > 0;
|
||||||
lastSetMessage = "Last set ${diffDays ? '${difference.inDays.toString()} days' : diffHours ? '${difference.inHours.toString()} hours' : diffMins ? '${difference.inMinutes.toString()} minutes' : '${difference.inSeconds.toString()} seconds'} ago";
|
final diffHours = difference.inHours > 0;
|
||||||
|
final diffMins = difference.inMinutes > 0;
|
||||||
|
lastSetMessage = "Last set ${diffDays ? '${difference.inDays.toString()} days' : diffHours ? '${difference.inHours.toString()} hours' : diffMins ? '${difference.inMinutes.toString()} minutes' : '${difference.inSeconds.toString()} seconds'} ago";
|
||||||
|
} else {
|
||||||
|
lastSetMessage = "Never set";
|
||||||
|
}
|
||||||
|
|
||||||
_blindPosition = (body['last_pos'] as int).toDouble();
|
_blindPosition = (body['last_pos'] as int).toDouble();
|
||||||
|
|
||||||
calibrated = true;
|
calibrated = true;
|
||||||
@@ -607,23 +613,31 @@ class _PeripheralScreenState extends State<PeripheralScreen> {
|
|||||||
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: LayoutBuilder(
|
||||||
margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05),
|
builder: (context, constraints) {
|
||||||
height: MediaQuery.of(context).size.width * 0.68,
|
final containerHeight = MediaQuery.of(context).size.width * 0.68;
|
||||||
width: MediaQuery.of(context).size.width * 0.7,
|
final maxSlatHeight = containerHeight / 10;
|
||||||
child: Column(
|
final slatHeight = _blindPosition < 5
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
? maxSlatHeight * (5 - _blindPosition) / 5
|
||||||
children: List.generate(10, (index) {
|
: maxSlatHeight * (_blindPosition - 5) / 5;
|
||||||
return AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
return Container(
|
||||||
height: _blindPosition < 5 ?
|
margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05),
|
||||||
5.4 * (5 - _blindPosition)
|
height: containerHeight,
|
||||||
: 5.4 * (_blindPosition - 5),
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
width: MediaQuery.of(context).size.width * 0.65, // example
|
child: Column(
|
||||||
color: const Color.fromARGB(255, 121, 85, 72),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
);
|
children: List.generate(10, (index) {
|
||||||
}),
|
return AnimatedContainer(
|
||||||
),
|
duration: const Duration(milliseconds: 300),
|
||||||
|
height: slatHeight,
|
||||||
|
width: MediaQuery.of(context).size.width * 0.65,
|
||||||
|
color: const Color.fromARGB(255, 121, 85, 72),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
|
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
|
||||||
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
|
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
|
||||||
import 'package:blind_master/BlindMasterScreens/day_time_picker.dart';
|
import 'package:blind_master/BlindMasterScreens/day_time_picker.dart';
|
||||||
|
import 'package:blind_master/main.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
@@ -28,10 +29,27 @@ class _SchedulesScreenState extends State<SchedulesScreen> {
|
|||||||
getSchedules();
|
getSchedules();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String translate(int pos) {
|
||||||
|
if (pos < 2) {
|
||||||
|
return "Close (down)";
|
||||||
|
} else if (pos < 5) {
|
||||||
|
return "Open (down)";
|
||||||
|
}
|
||||||
|
else if (pos == 5) {
|
||||||
|
return "Open";
|
||||||
|
}
|
||||||
|
else if (pos < 9) {
|
||||||
|
return "Open (up)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "Close (up)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future getSchedules() async {
|
Future getSchedules() async {
|
||||||
try{
|
try{
|
||||||
final payload = {
|
final payload = {
|
||||||
"periphId": widget.deviceId
|
"periphId": widget.peripheralId
|
||||||
};
|
};
|
||||||
final response = await securePost(payload, 'periph_schedule_list');
|
final response = await securePost(payload, 'periph_schedule_list');
|
||||||
if (response == null) throw Exception("no response!");
|
if (response == null) throw Exception("no response!");
|
||||||
@@ -100,23 +118,66 @@ class _SchedulesScreenState extends State<SchedulesScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onDismissed: (direction) {
|
onDismissed: (direction) async {
|
||||||
// TODO Actually delete the schedule
|
final scheduleId = schedule['id'].toString();
|
||||||
// deleteDevice(device['id'], i);
|
try {
|
||||||
|
final payload = {'jobId': scheduleId};
|
||||||
|
final response = await securePost(payload, 'delete_schedule');
|
||||||
|
|
||||||
|
if (response == null) throw Exception("Auth Error");
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception("Failed to delete schedule");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Schedule deleted successfully'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
|
||||||
|
} finally {
|
||||||
|
// Refresh the list regardless of success/failure
|
||||||
|
if (mounted) getSchedules();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
child: ListTile(
|
child: Builder(
|
||||||
leading: const Icon(Icons.blinds),
|
builder: (context) {
|
||||||
title: Text("${schedule['pos']} every ${schedule['schedule']['daysOfWeek']} at ${schedule['schedule']['hours']}:${schedule['schedule']['minutes']}"),
|
final pos = translate(schedule['pos']);
|
||||||
trailing: const Icon(Icons.arrow_forward_ios_rounded),
|
final days = (schedule['schedule']['daysOfWeek'] as List)
|
||||||
onTap: () {
|
.map((d) => DaysOfWeek.values[d].name)
|
||||||
Navigator.push(
|
.join(', ');
|
||||||
context,
|
final hour24 = schedule['schedule']['hours'][0] as int;
|
||||||
MaterialPageRoute(
|
final minute = schedule['schedule']['minutes'][0].toString().padLeft(2, '0');
|
||||||
builder: (context) => Placeholder(),
|
final period = hour24 >= 12 ? 'PM' : 'AM';
|
||||||
// TODO open popup for schedule setter.
|
final hour12 = hour24 == 0 ? 12 : (hour24 > 12 ? hour24 - 12 : hour24);
|
||||||
),
|
|
||||||
).then((_) { getSchedules(); });
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.blinds),
|
||||||
|
title: Text("$pos at $hour12:$minute $period"),
|
||||||
|
subtitle: Text(days),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios_rounded),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext dialogContext) {
|
||||||
|
return DayTimePicker(
|
||||||
|
defaultTime: TimeOfDay(hour: 12, minute: 0),
|
||||||
|
sendSchedule: sendSchedule,
|
||||||
|
peripheralId: widget.peripheralId,
|
||||||
|
peripheralNum: widget.peripheralNum,
|
||||||
|
deviceId: widget.deviceId,
|
||||||
|
existingSchedule: schedule,
|
||||||
|
scheduleId: schedule['id'].toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
).then((_) { getSchedules(); });
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -136,9 +197,15 @@ class _SchedulesScreenState extends State<SchedulesScreen> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext dialogContext) { // Use dialogContext for navigation within the dialog
|
builder: (BuildContext dialogContext) { // Use dialogContext for navigation within the dialog
|
||||||
return DayTimePicker(defaultTime: TimeOfDay(hour: 12, minute: 0), sendSchedule: sendSchedule);
|
return DayTimePicker(
|
||||||
|
defaultTime: TimeOfDay(hour: 12, minute: 0),
|
||||||
|
sendSchedule: sendSchedule,
|
||||||
|
peripheralId: widget.peripheralId,
|
||||||
|
peripheralNum: widget.peripheralNum,
|
||||||
|
deviceId: widget.deviceId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
).then((_) { getSchedules(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -340,10 +340,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -505,10 +505,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.7"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user