Merge pull request #80 from arrowsmith001/JHubi1/78-voice-scroll-text

Added scroll container to AI text on voice chat page
This commit is contained in:
JHubi1 2025-03-02 17:23:03 +01:00 committed by GitHub
commit ad719bc561
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 151 additions and 98 deletions

View File

@ -127,6 +127,7 @@ class _ScreenVoiceState extends State<ScreenVoice> {
setState(() { setState(() {
aiText = currentText; aiText = currentText;
lightHaptic(); lightHaptic();
updateScrollState();
}); });
if (done) { if (done) {
aiThinking = false; aiThinking = false;
@ -167,10 +168,23 @@ class _ScreenVoiceState extends State<ScreenVoice> {
: null); : null);
} }
final ScrollController scrollController = ScrollController();
bool atScrollEnd = false;
void updateScrollState() {
setState(() {
atScrollEnd = scrollController.position.extentAfter < 8.0;
});
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
scrollController.addListener(() {
updateScrollState();
});
resetSystemNavigation(context, resetSystemNavigation(context,
statusBarColor: themeDark().colorScheme.surface, statusBarColor: themeDark().colorScheme.surface,
systemNavigationBarColor: themeDark().colorScheme.surface, systemNavigationBarColor: themeDark().colorScheme.surface,
@ -212,6 +226,7 @@ class _ScreenVoiceState extends State<ScreenVoice> {
}, },
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
scrolledUnderElevation: 0.0,
leading: IconButton( leading: IconButton(
enableFeedback: false, enableFeedback: false,
onPressed: () { onPressed: () {
@ -252,107 +267,145 @@ class _ScreenVoiceState extends State<ScreenVoice> {
color: Colors.grey, color: Colors.grey,
)) ))
]), ]),
body: Column( body: SafeArea(
mainAxisSize: MainAxisSize.max, child: Column(
children: [ mainAxisSize: MainAxisSize.max,
Expanded( children: [
child: Column(mainAxisSize: MainAxisSize.max, children: [ Expanded(
Expanded( child: Column(mainAxisSize: MainAxisSize.max, children: [
child: Padding( Expanded(
padding: const EdgeInsets.only(left: 16, right: 16), child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Center(
child: Text(text,
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: const TextStyle(
color: Colors.grey,
fontFamily: "monospace"))),
))
]),
),
Expanded(
child: Center( child: Center(
child: Text(text, child: DateTimeLoopBuilder(
textAlign: TextAlign.center, timeUnit: TimeUnit.seconds,
overflow: TextOverflow.fade, builder: (context, dateTime, child) {
style: const TextStyle( return SizedBox(
color: Colors.grey, height: 96,
fontFamily: "monospace"))), width: 96,
)) child: AnimatedScale(
]), scale: speaking
), ? aiThinking
Expanded( ? (dateTime.second).isEven
child: Center( ? 2.4
child: DateTimeLoopBuilder( : 2
timeUnit: TimeUnit.seconds, : 2
builder: (context, dateTime, child) { : dateTime.second
return SizedBox( .toString()
height: 96, .endsWith("1")
width: 96, ? 1.6
child: AnimatedScale( : 1.4,
scale: speaking duration: aiThinking
? aiThinking ? const Duration(seconds: 1)
? (dateTime.second).isEven : const Duration(milliseconds: 200),
? 2.4 curve: Curves.easeInOut,
: 2 child: InkWell(
: 2 borderRadius:
: dateTime.second BorderRadius.circular(48),
.toString() onTap: () {
.endsWith("1") if (speaking && !aiThinking) {
? 1.6 intendedStop = true;
: 1.4, speaking = false;
duration: aiThinking voice.stop();
? const Duration(seconds: 1) return;
: const Duration(milliseconds: 200), }
curve: Curves.easeInOut, process();
child: InkWell( },
borderRadius: child: CircleAvatar(
BorderRadius.circular(48), backgroundColor: themeDark()
onTap: () { .colorScheme
if (speaking && !aiThinking) { .primary
intendedStop = true;
speaking = false;
voice.stop();
return;
}
process();
},
child: CircleAvatar(
backgroundColor: themeDark()
.colorScheme
.primary
.withAlpha( .withAlpha(
!speaking ? 200 : 255), !speaking ? 200 : 255),
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration( duration: const Duration(
milliseconds: 200), milliseconds: 200),
child: speaking child: speaking
? aiThinking ? aiThinking
? Icon(Icons.auto_awesome_rounded, ? Icon(Icons.auto_awesome_rounded,
color: themeDark() color: themeDark()
.colorScheme .colorScheme
.secondary, .secondary,
key: const ValueKey( key: const ValueKey(
"aiThinking")) "aiThinking"))
: sttDone : sttDone
? Icon(Icons.volume_up_rounded, ? Icon(Icons.volume_up_rounded,
color: themeDark() color: themeDark()
.colorScheme .colorScheme
.secondary, .secondary,
key: const ValueKey( key: const ValueKey(
"tts")) "tts"))
: Icon(Icons.mic_rounded, : Icon(Icons.mic_rounded,
color: themeDark() color: themeDark()
.colorScheme .colorScheme
.secondary, .secondary,
key: const ValueKey("stt")) key: const ValueKey("stt"))
: null)))), : null)))),
); );
}))), }))),
Expanded( Expanded(
child: Column(mainAxisSize: MainAxisSize.max, children: [ child: Column(mainAxisSize: MainAxisSize.max, children: [
Expanded( Expanded(
child: Padding( child: Stack(
padding: const EdgeInsets.only(left: 16, right: 16), children: [
child: Center( ShaderMask(
child: Text(aiText, shaderCallback: (Rect bounds) {
textAlign: TextAlign.center, return LinearGradient(
overflow: TextOverflow.fade, begin: Alignment.bottomCenter,
style: const TextStyle( end: Alignment.topCenter,
fontFamily: "monospace"))), colors: const [
)) Colors.transparent,
]), Colors.black
) ],
], stops: [0.0, atScrollEnd ? 0.0 : 0.1],
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: SingleChildScrollView(
controller: scrollController,
child: Padding(
padding: const EdgeInsets.only(
left: 16, right: 16),
child: Center(
child: Text(aiText,
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: const TextStyle(
fontFamily:
"monospace")))))),
if (!atScrollEnd)
Positioned(
right: 0,
bottom: 0,
child: IconButton(
icon: const Icon(
Icons.arrow_downward_rounded,
color: Colors.grey),
onPressed: () {
scrollController.animateTo(
scrollController
.position.maxScrollExtent,
duration: const Duration(
milliseconds: 500),
curve: Curves.easeInOut);
}),
)
],
))
]),
)
]),
)))); ))));
} }
} }