skip to Main Content

I am trying to implement a whatsapp like chat box with voice message feature exactly like in whatsapp, the ui design for voice message is working fine, but the delete,send,pause button inside the Positioned widgets are not working(these buttons used when recording audio using the lock concept exactly in whatsapp) when I click these buttons or inside the Positioned widgets the keyboard pops up for the underlying message box, How can I avoid this and make the buttons working when click.
below is my code ;

main.dart :

class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
late AnimationController controller;

@override
void initState() {
    super.initState();
    controller = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 600),
    );
}

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

@override
Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
        title: const Text("Audio Chat"),
    ),
    body: Padding(
        padding: const EdgeInsets.all(Globals.defaultPadding),
        child: Column(
        children: [
            const Expanded(child: AudioList()),
            Row(
            mainAxisSize: MainAxisSize.max,
            children: [
                ChatBox(controller: controller),
                const SizedBox(width: 4),
                RecordButton(controller: controller),
            ],
            ),
        ],
        ),
    ),
    );
}
}

record_button.dart :

class _RecordButtonState extends State<RecordButton> {
static const double size = 55;

final double lockerHeight = 200;
double timerWidth = 0;

late Animation<double> buttonScaleAnimation;
late Animation<double> timerAnimation;
late Animation<double> lockerAnimation;

DateTime? startTime;
Timer? timer;
String recordDuration = "00:00";
late Record record;

bool isLocked = false;
bool showLottie = false;

@override
void initState() {
super.initState();
buttonScaleAnimation = Tween<double>(begin: 1, end: 2).animate(
  CurvedAnimation(
    parent: widget.controller,
    curve: const Interval(0.0, 0.6, curve: Curves.elasticInOut),
  ),
);
widget.controller.addListener(() {
  setState(() {});
});
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
timerWidth = MediaQuery.of(context).size.width - 2 * Globals.defaultPadding - 4 + 5;
timerAnimation =
    Tween<double>(begin: timerWidth + Globals.defaultPadding, end: 0)
        .animate(
      CurvedAnimation(
        parent: widget.controller,
        curve: const Interval(0.2, 1, curve: Curves.easeIn),
      ),
    );
lockerAnimation =
    Tween<double>(begin: lockerHeight + Globals.defaultPadding, end: 0)
        .animate(
      CurvedAnimation(
        parent: widget.controller,
        curve: const Interval(0.2, 1, curve: Curves.easeIn),
      ),
    );
}

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

@override
Widget build(BuildContext context) {
return Stack(
  clipBehavior: Clip.none,
  children: [
    lockSlider(),
    cancelSlider(),
    audioButton(),
    if (isLocked) timerLocked(),
  ],
);
}

Widget lockSlider() {
return Positioned(
  bottom: -lockerAnimation.value,
  child: Container(
    height: lockerHeight,
    width: size,
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(Globals.borderRadius),
      color: Colors.black,
    ),
    padding: const EdgeInsets.symmetric(vertical: 15),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: [
        const Icon(FontAwesomeIcons.lock, size: 20),
        const SizedBox(height: 8),
        FlowShader(
          direction: Axis.vertical,
          child: Column(
            children: const [
              Icon(Icons.keyboard_arrow_up),
              Icon(Icons.keyboard_arrow_up),
              Icon(Icons.keyboard_arrow_up),
            ],
          ),
        ),
      ],
    ),
  ),
);
}

Widget cancelSlider() {
return Positioned(
  right: -timerAnimation.value,
  child: Container(
    height: size,
    width: timerWidth,
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(Globals.borderRadius),
      color: Colors.black,
    ),
    child: Padding(
      padding: const EdgeInsets.symmetric(horizontal: 15),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        mainAxisSize: MainAxisSize.max,
        children: [
          showLottie ? const LottieAnimation() : Text(recordDuration),
          const SizedBox(width: size),
          FlowShader(
            child: Row(
              children: const [
                Icon(Icons.keyboard_arrow_left),
                Text("Slide to cancel")
              ],
            ),
            duration: const Duration(seconds: 3),
            flowColors: const [Colors.white, Colors.grey],
          ),
          const SizedBox(width: size),
        ],
      ),
    ),
  ),
);
}

Widget timerLocked() {
return Positioned(
  right: 0,
  bottom: 0,
  child: Container(
    height: 80,
    width: timerWidth,
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(Globals.borderRadius - 10),
      color: Colors.lightBlue,
    ),
    child: Padding(
      padding: const EdgeInsets.only(left: 15, right: 25),
      child: Column(
        children: [
          Flexible(
            flex: 1,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              mainAxisSize: MainAxisSize.max,
              children: [
                Text(recordDuration),
                FlowShader(
                  child: const Text("Tap lock to stop"),
                  duration: const Duration(seconds: 3),
                  flowColors: const [Colors.white, Colors.grey],
                ),
                GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () async {
                    print('Recording finished');
                  },
                  child: const Center(
                    child: Icon(
                      FontAwesomeIcons.check,
                      size: 18,
                      color: Colors.black,
                    ),
                  ),
                ),
              ],
            ),
          ),
          Flexible(
            flex: 1,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              mainAxisSize: MainAxisSize.max,
              children: [
                Center(
                  child: InkWell(
                    onTap: () {
                      print('Action Delete');
                    },
                    child: Icon(FontAwesomeIcons.trash,size: 18,color: Colors.black,),
                  ),
                ),
                GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () async {
                    print('Action Pause');
                  },
                  child: const Center(
                    child: Icon(
                      FontAwesomeIcons.pause,
                      size: 18,
                      color: Colors.black,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    ),
  ),
);
}

Widget audioButton() {
return GestureDetector(
  child: Transform.scale(
    scale: buttonScaleAnimation.value,
    child: Container(
      child: const Icon(Icons.mic),
      height: size,
      width: size,
      clipBehavior: Clip.hardEdge,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: Theme.of(context).primaryColor,
      ),
    ),
  ),
  onLongPressDown: (_) {
    widget.controller.forward();
  },
  onLongPressEnd: (details) async {

    if (isCancelled(details.localPosition, context)) {

    } else if (checkIsLocked(details.localPosition)) {
      widget.controller.reverse();

      Vibrate.feedback(FeedbackType.heavy);
      setState(() {
        isLocked = true;
      });
    } else {
      print('Recording Finished');
    }
  },
  onLongPressCancel: () {
    widget.controller.reverse();
  },
  onLongPress: () async {
    Vibrate.feedback(FeedbackType.success);
    if (await Record().hasPermission()) {
      record = Record();
      await record.start(
        path: Globals.documentPath +
            "audio_${DateTime.now().millisecondsSinceEpoch}.m4a",
        encoder: AudioEncoder.aacEld,
        bitRate: 128000,
        samplingRate: 44100,
      );
      startTime = DateTime.now();
      timer = Timer.periodic(const Duration(seconds: 1), (_) {
        final minDur = DateTime.now().difference(startTime!).inMinutes;
        final secDur = DateTime.now().difference(startTime!).inSeconds % 60;
        String min = minDur < 10 ? "0$minDur" : minDur.toString();
        String sec = secDur < 10 ? "0$secDur" : secDur.toString();
        setState(() {
          recordDuration = "$min:$sec";
        });
      });
    }
  },
);
}

bool checkIsLocked(Offset offset) {
return (offset.dy < -35);
}

bool isCancelled(Offset offset, BuildContext context) {
return (offset.dx < -(MediaQuery.of(context).size.width * 0.2));
}
}

UPDATE :

The only work around i found till now is to enclose the timerLocked Positioned widget with Stack,SizedBox,Positioned as below.

    Widget timerLocked() {
return Positioned(
  child: SizedBox(
    height: 80,
    width: timerWidth,
    child: Stack(
      children: [
        Positioned(
        right: 0,
        bottom: 0,
        height: 80,
        width: timerWidth,
        child: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(Globals.borderRadius - 10),
          color: Colors.lightBlue,
        ),
        child: Padding(
          padding: const EdgeInsets.only(left: 15, right: 25),
          child: Column(
            children: [
              
            ],
          ),
        ),),
        ),
      ],),),);
}

2

Answers


  1. Chosen as BEST ANSWER

    Finally I found a solution.I replaced Positioned widget with ExpandTapWidget(ExpandTapWidget) and that solved the problem.This was a bug/documentaion issue and is posted here which helped me to solve this.Below is the code,

    Widget timerLocked() {
      return ExpandTapWidget(
      onTap: () {
        print('********** ExpandTapWidget Clicked ********');
      },
      tapPadding: EdgeInsets.all(1),
      child: Container(
              height: 80,
              width: timerWidth,
              decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(Globals.borderRadius - 10),
              color: Colors.lightBlue,
              ),
              child: Padding(
              padding: const EdgeInsets.only(left: 15, right: 25),
              child: Column(
                children: [
                  
                ],
              ),
              ),),
            );
    }
    

  2. To fix this issue, we need to wrap the buttons with an IgnorePointer widget and make sure that the gestures are not passed to the underlying widgets.

    // ...
    //...
    Flexible(
          flex: 1,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            mainAxisSize: MainAxisSize.max,
            children: [
              IgnorePointer(
                ignoring: false,
                child: Center(
                  child: InkWell(
                    onTap: () {
                      print('Action Delete');
                    },
                    child: Icon(
                      FontAwesomeIcons.trash,
                      size: 18,
                      color: Colors.black,
                    ),
                  ),
                ),
              ),
              IgnorePointer(
                ignoring: false,
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () async {
                    print('Action Pause');
                  },
                  child: const Center(
                    child: Icon(
                      FontAwesomeIcons.pause,
                      size: 18,
                      color: Colors.black,
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search