Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions lib/widgets/resources/arm_new.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import '../../viam_sdk.dart';
import 'arm_widgets/joint_positions_widget.dart';
import 'arm_widgets/pose_widget.dart';

class ArmNotifier extends ChangeNotifier {
void armHasMoved() {
notifyListeners();
}
}

/// A widget to control an [Arm].
class ViamArmWidgetNew extends StatelessWidget {
class ViamArmWidgetNew extends StatefulWidget {
/// The [Arm]
final Arm arm;

Expand All @@ -14,12 +20,25 @@ class ViamArmWidgetNew extends StatelessWidget {
required this.arm,
});

@override
State<ViamArmWidgetNew> createState() => _ViamArmWidgetNewState();
}

class _ViamArmWidgetNewState extends State<ViamArmWidgetNew> {
final ArmNotifier _armNotifier = ArmNotifier();

@override
void dispose() {
_armNotifier.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Column(
children: [
JointPositionsWidget(arm: arm),
PoseWidget(arm: arm),
JointPositionsWidget(arm: widget.arm, updateNotifier: _armNotifier),
PoseWidget(arm: widget.arm, updateNotifier: _armNotifier),
],
);
}
Expand Down
164 changes: 92 additions & 72 deletions lib/widgets/resources/arm_widgets/joint_positions_widget.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,82 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import '../../../viam_sdk.dart' as viam;

import '../arm_new.dart';

class JointPositionsWidget extends StatefulWidget {
final viam.Arm arm;
const JointPositionsWidget({super.key, required this.arm});
final ArmNotifier updateNotifier;
const JointPositionsWidget({
super.key,
required this.arm,
required this.updateNotifier,
});

@override
State<JointPositionsWidget> createState() => _JointPositionsWidgetState();
}

bool _isLive = false;

class _JointPositionsWidgetState extends State<JointPositionsWidget> {
List<double> _jointValues = [];
bool _isLive = false;
List<TextEditingController> _textControllers = [];

@override
void initState() {
super.initState();
widget.updateNotifier.addListener(_getJointPositions);
_getJointPositions();
}

@override
void dispose() {
widget.updateNotifier.removeListener(_getJointPositions);
for (final controller in _textControllers) {
controller.dispose();
}
super.dispose();
}

Future<void> _getJointPositions() async {
for (final controller in _textControllers) {
controller.dispose();
}

_jointValues = await widget.arm.jointPositions();
setState(() {});
_textControllers = List.generate(
_jointValues.length,
(index) => TextEditingController(text: _jointValues[index].toStringAsFixed(1)),
);
if (mounted) {
setState(() {});
}
}

void _updateJointValue(int index, double value) {
const double minPosition = -359.0;
const double maxPosition = 359.0;
final clampedValue = value.clamp(minPosition, maxPosition);

setState(() {
_jointValues[index] = clampedValue;
final formattedValue = clampedValue.toStringAsFixed(1);
if (_textControllers[index].text != formattedValue) {
_textControllers[index].text = formattedValue;
_textControllers[index].selection = TextSelection.fromPosition(
TextPosition(offset: _textControllers[index].text.length),
);
}
});

if (_isLive) {
_setJointPositions();
}
}

Future<void> _setJointPositions() async {
await widget.arm.moveToJointPositions(_jointValues);
widget.updateNotifier.armHasMoved();
}

@override
Expand All @@ -50,7 +98,18 @@ class _JointPositionsWidgetState extends State<JointPositionsWidget> {
children: _jointValues.isEmpty
? [CircularProgressIndicator.adaptive()]
: List.generate(_jointValues.length, (index) {
return _BuildJointControlRow(index: index, arm: widget.arm, startJointValues: _jointValues);
return _BuildJointControlRow(
index: index,
value: _jointValues[index],
controller: _textControllers[index],
onSliderChanged: (newValue) => _updateJointValue(index, newValue),
onSubmitted: (newValue) {
final parsedValue = double.tryParse(newValue) ?? _jointValues[index];
_updateJointValue(index, parsedValue);
},
onDecrement: () => _updateJointValue(index, _jointValues[index] - 1.0),
onIncrement: () => _updateJointValue(index, _jointValues[index] + 1.0),
);
}),
),
),
Expand Down Expand Up @@ -84,59 +143,27 @@ class _JointPositionsWidgetState extends State<JointPositionsWidget> {
}
}

class _BuildJointControlRow extends StatefulWidget {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved a lot of logic out of this widget and up a level, so now this widget is stateless and this widget doesn't have to be concerned with the notifier

final int index;
final viam.Arm arm;
final List<double> startJointValues;
const _BuildJointControlRow({required this.index, required this.arm, required this.startJointValues});

@override
State<_BuildJointControlRow> createState() => _BuildJointControlRowState();
}

class _BuildJointControlRowState extends State<_BuildJointControlRow> {
class _BuildJointControlRow extends StatelessWidget {
static const double _minPosition = -359.0;
static const double _maxPosition = 359.0;

List<double> _jointValues = [];
List<TextEditingController> _textControllers = [];

@override
void initState() {
_jointValues = widget.startJointValues;
_textControllers = List.generate(
_jointValues.length,
(index) => TextEditingController(text: _jointValues[index].toStringAsFixed(1)),
);
super.initState();
}

@override
void dispose() {
for (final controller in _textControllers) {
controller.dispose();
}
super.dispose();
}

void _updateJointValue(int index, double value) {
final clampedValue = value.clamp(_minPosition, _maxPosition);

setState(() {
_jointValues[index] = clampedValue;
final formattedValue = clampedValue.toStringAsFixed(1);
if (_textControllers[index].text != formattedValue) {
_textControllers[index].text = formattedValue;
_textControllers[index].selection = TextSelection.fromPosition(
TextPosition(offset: _textControllers[index].text.length),
);
}
});

if (_isLive) {
widget.arm.moveToJointPositions(_jointValues);
}
}
final int index;
final double value;
final TextEditingController controller;
final ValueChanged<double> onSliderChanged;
final ValueChanged<String> onSubmitted;
final VoidCallback onIncrement;
final VoidCallback onDecrement;

const _BuildJointControlRow({
required this.index,
required this.value,
required this.controller,
required this.onSliderChanged,
required this.onSubmitted,
required this.onIncrement,
required this.onDecrement,
});

@override
Widget build(BuildContext context) {
Expand All @@ -147,46 +174,39 @@ class _BuildJointControlRowState extends State<_BuildJointControlRow> {
SizedBox(
width: 30,
child: Text(
'J${widget.index + 1}',
'J${index + 1}',
style: Theme.of(context).textTheme.titleMedium,
),
),
Expanded(
child: Slider(
value: _jointValues[widget.index],
value: value,
min: _minPosition,
max: _maxPosition,
divisions: (_maxPosition - _minPosition).toInt(),
onChanged: (newValue) => _updateJointValue(widget.index, newValue),
divisions: (_maxPosition - _minPosition).round(),
onChanged: onSliderChanged,
),
),
SizedBox(
width: 70,
child: TextField(
controller: _textControllers[widget.index],
controller: controller,
textAlign: TextAlign.center,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,1}')),
],
onSubmitted: (newValue) {
final parsedValue = double.tryParse(newValue) ?? _jointValues[widget.index];
_updateJointValue(widget.index, parsedValue);
},
onSubmitted: onSubmitted,
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
_updateJointValue(widget.index, _jointValues[widget.index] - 1.0);
},
onPressed: onDecrement,
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
_updateJointValue(widget.index, _jointValues[widget.index] + 1.0);
},
onPressed: onIncrement,
),
],
),
Expand Down
Loading