|
|
|
|
import 'dart:async';
|
|
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart' show AnimationConfiguration, SlideAnimation, FadeInAnimation;
|
|
|
|
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/app_state.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
|
|
|
|
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
|
|
|
|
|
|
|
|
|
import 'lab_order_by_test.dart';
|
|
|
|
|
|
|
|
|
|
class AlphabeticScroll extends StatefulWidget{
|
|
|
|
|
final List<String> alpahbetsAvailable;
|
|
|
|
|
final List<TestDetails> details;
|
|
|
|
|
final AppState appState;
|
|
|
|
|
final LabViewModel labViewModel;
|
|
|
|
|
final DateRangeSelectorRangeViewModel rangeViewModel;
|
|
|
|
|
|
|
|
|
|
const AlphabeticScroll({super.key, required this.alpahbetsAvailable, required this.details, required this.appState, required this.labViewModel, required this.rangeViewModel});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<AlphabeticScroll> createState() => _AlphabetScrollPageState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _AlphabetScrollPageState extends State<AlphabeticScroll> {
|
|
|
|
|
final ItemScrollController itemScrollController = ItemScrollController();
|
|
|
|
|
final ScrollOffsetController scrollOffsetController = ScrollOffsetController();
|
|
|
|
|
final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
|
|
|
|
|
final ScrollOffsetListener scrollOffsetListener = ScrollOffsetListener.create();
|
|
|
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
|
|
|
|
|
|
Map<String, List<TestDetails>> data = {};
|
|
|
|
|
Map<String, num> density = {};
|
|
|
|
|
|
|
|
|
|
Map<String, double> _offsetMap = {};
|
|
|
|
|
Map<String, GlobalKey> _offsetKeys = {};
|
|
|
|
|
int _activeIndex = 0; // <-- Highlighted letter
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
scheduleMicrotask((){
|
|
|
|
|
for(var char in widget.alpahbetsAvailable){
|
|
|
|
|
data[char] = widget.details.where((element)=>element.description?.toLowerCase().startsWith(char.toLowerCase()) == true).toList();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
setState((){});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
itemPositionsListener.itemPositions.addListener((){
|
|
|
|
|
|
|
|
|
|
final positions = itemPositionsListener.itemPositions.value;
|
|
|
|
|
|
|
|
|
|
if (positions.isEmpty) return;
|
|
|
|
|
|
|
|
|
|
// Get FIRST visible item (top-most)
|
|
|
|
|
final firstVisible = positions
|
|
|
|
|
.where((p) => p.itemTrailingEdge > 0) // visible
|
|
|
|
|
.reduce((min, p) =>
|
|
|
|
|
p.itemLeadingEdge < min.itemLeadingEdge ? p : min);
|
|
|
|
|
|
|
|
|
|
if(_activeIndex == firstVisible.index) return ;
|
|
|
|
|
setState(() {
|
|
|
|
|
_activeIndex = firstVisible.index;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
print("Active index = $_activeIndex");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
itemPositionsListener.itemPositions.removeListener((){
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
super.dispose();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _scrollToLetter(String letter) async {
|
|
|
|
|
|
|
|
|
|
// itemScrollController.jumpTo(index:density[letter]?.toInt()??0, );
|
|
|
|
|
itemScrollController.scrollTo(
|
|
|
|
|
index: data.keys.toList().indexOf(letter).toInt()??0,
|
|
|
|
|
duration: Duration(seconds: 2),
|
|
|
|
|
curve: Curves.easeInOutCubic);
|
|
|
|
|
// final key = _offsetKeys[letter];
|
|
|
|
|
// if (key == null) return;
|
|
|
|
|
//
|
|
|
|
|
// BuildContext? ctx = key.currentContext;
|
|
|
|
|
//
|
|
|
|
|
// // Retry until built (max 100ms)
|
|
|
|
|
// int retry = 0;
|
|
|
|
|
// while (ctx == null && retry < 5) {
|
|
|
|
|
// await Future.delayed(Duration(milliseconds: 20));
|
|
|
|
|
// ctx = key.currentContext;
|
|
|
|
|
// retry++;
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if (ctx == null) {
|
|
|
|
|
// print("❌ $letter still not built");
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// final renderBox = ctx.findRenderObject() as RenderBox;
|
|
|
|
|
// final yOffset = renderBox.localToGlobal(Offset.zero).dy;
|
|
|
|
|
//
|
|
|
|
|
// _scrollController.animateTo(
|
|
|
|
|
// _scrollController.offset + yOffset - 80,
|
|
|
|
|
// duration: Duration(milliseconds: 400),
|
|
|
|
|
// curve: Curves.easeInOut,
|
|
|
|
|
// );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: MediaQuery.sizeOf(context).width,
|
|
|
|
|
child: Row(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, // Add this
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Column(
|
|
|
|
|
children: [
|
|
|
|
|
SizedBox(
|
|
|
|
|
height: (MediaQuery.sizeOf(context).height),
|
|
|
|
|
child: _buildList(widget.alpahbetsAvailable)
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 24.w,
|
|
|
|
|
height: MediaQuery.sizeOf(context).height-(70.h+ kToolbarHeight+100.h),
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisSize: MainAxisSize.max, // Add this
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center, // Changed from center to start
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
children:List.generate(widget.alpahbetsAvailable.length, (i) {
|
|
|
|
|
final isActive = (i == _activeIndex);
|
|
|
|
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
|
onTap: () {
|
|
|
|
|
setState(() => _activeIndex = i);
|
|
|
|
|
_scrollToLetter(widget.alpahbetsAvailable[i]);
|
|
|
|
|
},
|
|
|
|
|
child: TweenAnimationBuilder<double>(
|
|
|
|
|
tween: Tween(begin: 1.0, end: isActive ? 1.8 : 1.0),
|
|
|
|
|
duration: Duration(milliseconds: 120),
|
|
|
|
|
curve: Curves.easeOut,
|
|
|
|
|
builder: (_, scale, child) {
|
|
|
|
|
return Transform.scale(
|
|
|
|
|
scale: scale,
|
|
|
|
|
child: Opacity(
|
|
|
|
|
opacity: isActive ? 1.0 : 0.5,
|
|
|
|
|
child: widget.alpahbetsAvailable[i].toUpperCase().toText14(
|
|
|
|
|
color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
// );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildList(List<String> alphabet) {
|
|
|
|
|
return ScrollablePositionedList.builder(
|
|
|
|
|
shrinkWrap: true,
|
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
|
itemScrollController: itemScrollController,
|
|
|
|
|
scrollOffsetController: scrollOffsetController,
|
|
|
|
|
itemPositionsListener: itemPositionsListener,
|
|
|
|
|
scrollOffsetListener: scrollOffsetListener,
|
|
|
|
|
itemCount: data.length,
|
|
|
|
|
itemBuilder: (_, index) {
|
|
|
|
|
final letter = alphabet[index].toLowerCase();
|
|
|
|
|
final items = data[letter]!;
|
|
|
|
|
return Container(
|
|
|
|
|
key: _offsetKeys[letter],
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children:
|
|
|
|
|
List.generate(items.length,(itemIndex)=>
|
|
|
|
|
AnimationConfiguration.staggeredList(
|
|
|
|
|
position: index,
|
|
|
|
|
duration: const Duration(milliseconds: 500),
|
|
|
|
|
child: SlideAnimation(
|
|
|
|
|
verticalOffset: 100.0,
|
|
|
|
|
child: FadeInAnimation(
|
|
|
|
|
child: LabOrderByTest(
|
|
|
|
|
|
|
|
|
|
appState: getIt<AppState>(),
|
|
|
|
|
onTap: () {
|
|
|
|
|
if (items[itemIndex].model != null) {
|
|
|
|
|
widget.rangeViewModel.flush();
|
|
|
|
|
widget.labViewModel.getPatientLabResult(items[itemIndex].model!, items[itemIndex].description!,
|
|
|
|
|
(widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!),"");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
tests: items[itemIndex],
|
|
|
|
|
index: itemIndex,
|
|
|
|
|
isExpanded: true)),
|
|
|
|
|
),
|
|
|
|
|
))
|
|
|
|
|
// ...items.indexed((item) =>
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|