avoid using builders
This commit is contained in:
@@ -52,6 +52,112 @@ import 'package:weblibre/features/geckoview/features/readerview/presentation/wid
|
|||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
|
class _TabBar extends HookConsumerWidget {
|
||||||
|
final ValueNotifier<bool> showAppBar;
|
||||||
|
final ValueNotifier<PersistentBottomSheetController?> sheetController;
|
||||||
|
|
||||||
|
const _TabBar({required this.showAppBar, required this.sheetController});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final tabId = ref.watch(selectedTabProvider);
|
||||||
|
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
||||||
|
|
||||||
|
final tabInFullScreen = ref.watch(
|
||||||
|
selectedTabStateProvider.select((value) => value?.isFullScreen ?? false),
|
||||||
|
);
|
||||||
|
|
||||||
|
final autoHideTabBar = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.autoHideTabBar,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!autoHideTabBar) {
|
||||||
|
return Visibility(
|
||||||
|
visible: !tabInFullScreen,
|
||||||
|
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final appBarVisible = useValueListenable(showAppBar);
|
||||||
|
final diffAcc = useRef(0.0);
|
||||||
|
|
||||||
|
void resetHiddenState() {
|
||||||
|
showAppBar.value = true;
|
||||||
|
diffAcc.value = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
resetHiddenState();
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [tabId]);
|
||||||
|
|
||||||
|
useOnAppLifecycleStateChange((previous, current) {
|
||||||
|
if (current == AppLifecycleState.resumed) {
|
||||||
|
resetHiddenState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.listen(tabStateProvider(tabId).select((value) => value?.isLoading), (
|
||||||
|
previous,
|
||||||
|
next,
|
||||||
|
) {
|
||||||
|
if (next == true) {
|
||||||
|
resetHiddenState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.listen(tabStateProvider(tabId).select((value) => value?.historyState), (
|
||||||
|
previous,
|
||||||
|
next,
|
||||||
|
) {
|
||||||
|
if (next != null && previous != null) {
|
||||||
|
if (previous != next) {
|
||||||
|
resetHiddenState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.listen(tabScrollYProvider(tabId, const Duration(milliseconds: 50)), (
|
||||||
|
previous,
|
||||||
|
next,
|
||||||
|
) {
|
||||||
|
if (previous?.value != null && next.value != null) {
|
||||||
|
final diff = previous!.value! - next.value!;
|
||||||
|
if (diff < 0) {
|
||||||
|
if (diffAcc.value > 0) {
|
||||||
|
diffAcc.value = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
diffAcc.value += diff;
|
||||||
|
if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
|
||||||
|
showAppBar.value = false;
|
||||||
|
}
|
||||||
|
} else if (diff > 0) {
|
||||||
|
if (diffAcc.value < 0) {
|
||||||
|
diffAcc.value = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
diffAcc.value += diff;
|
||||||
|
if (diffAcc.value.abs() > (kToolbarHeight / 2)) {
|
||||||
|
resetHiddenState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Visibility(
|
||||||
|
visible:
|
||||||
|
sheetController.value != null || (!tabInFullScreen && appBarVisible),
|
||||||
|
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class BrowserScreen extends HookConsumerWidget {
|
class BrowserScreen extends HookConsumerWidget {
|
||||||
const BrowserScreen({super.key});
|
const BrowserScreen({super.key});
|
||||||
|
|
||||||
@@ -114,107 +220,9 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
//This causes issues with a non dismissable barrier pushed, we ahve our own barrier and this does seem to have issues when dismissing, so disable it completely
|
//This causes issues with a non dismissable barrier pushed, we ahve our own barrier and this does seem to have issues when dismissing, so disable it completely
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
bottomNavigationBar: HookConsumer(
|
bottomNavigationBar: _TabBar(
|
||||||
builder: (context, ref, child) {
|
showAppBar: showAppBar,
|
||||||
final tabId = ref.watch(selectedTabProvider);
|
sheetController: sheetController,
|
||||||
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
|
||||||
|
|
||||||
final tabInFullScreen = ref.watch(
|
|
||||||
selectedTabStateProvider.select(
|
|
||||||
(value) => value?.isFullScreen ?? false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final autoHideTabBar = ref.watch(
|
|
||||||
generalSettingsWithDefaultsProvider.select(
|
|
||||||
(value) => value.autoHideTabBar,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!autoHideTabBar) {
|
|
||||||
return Visibility(
|
|
||||||
visible: !tabInFullScreen,
|
|
||||||
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final appBarVisible = useValueListenable(showAppBar);
|
|
||||||
final diffAcc = useRef(0.0);
|
|
||||||
|
|
||||||
void resetHiddenState() {
|
|
||||||
showAppBar.value = true;
|
|
||||||
diffAcc.value = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
resetHiddenState();
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [tabId]);
|
|
||||||
|
|
||||||
useOnAppLifecycleStateChange((previous, current) {
|
|
||||||
if (current == AppLifecycleState.resumed) {
|
|
||||||
resetHiddenState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ref.listen(
|
|
||||||
tabStateProvider(tabId).select((value) => value?.isLoading),
|
|
||||||
(previous, next) {
|
|
||||||
if (next == true) {
|
|
||||||
resetHiddenState();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.listen(
|
|
||||||
tabStateProvider(tabId).select((value) => value?.historyState),
|
|
||||||
(previous, next) {
|
|
||||||
if (next != null && previous != null) {
|
|
||||||
if (previous != next) {
|
|
||||||
resetHiddenState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.listen(
|
|
||||||
tabScrollYProvider(tabId, const Duration(milliseconds: 50)),
|
|
||||||
(previous, next) {
|
|
||||||
if (previous?.value != null && next.value != null) {
|
|
||||||
final diff = previous!.value! - next.value!;
|
|
||||||
if (diff < 0) {
|
|
||||||
if (diffAcc.value > 0) {
|
|
||||||
diffAcc.value = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffAcc.value += diff;
|
|
||||||
if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
|
|
||||||
showAppBar.value = false;
|
|
||||||
}
|
|
||||||
} else if (diff > 0) {
|
|
||||||
if (diffAcc.value < 0) {
|
|
||||||
diffAcc.value = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffAcc.value += diff;
|
|
||||||
if (diffAcc.value.abs() > (kToolbarHeight / 2)) {
|
|
||||||
resetHiddenState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return Visibility(
|
|
||||||
visible:
|
|
||||||
sheetController.value != null ||
|
|
||||||
(!tabInFullScreen && appBarVisible),
|
|
||||||
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
body: _Browser(
|
body: _Browser(
|
||||||
overlayController: overlayController,
|
overlayController: overlayController,
|
||||||
|
|||||||
@@ -370,6 +370,202 @@ class _TabViewHeader extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TabView extends HookConsumerWidget {
|
||||||
|
final ScrollController scrollController;
|
||||||
|
final VoidCallback onClose;
|
||||||
|
|
||||||
|
const _TabView({required this.scrollController, required this.onClose});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
final containerId = ref.watch(selectedContainerProvider);
|
||||||
|
|
||||||
|
final filteredTabEntities = ref.watch(
|
||||||
|
seamlessFilteredTabEntitiesProvider(
|
||||||
|
searchPartition: TabSearchPartition.preview,
|
||||||
|
// ignore: document_ignores using fast equatable
|
||||||
|
// ignore: provider_parameters
|
||||||
|
containerFilter: ContainerFilterById(containerId: containerId),
|
||||||
|
groupTrees: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final tabSuggestionsEnabled = ref.watch(tabSuggestionsControllerProvider);
|
||||||
|
|
||||||
|
final suggestedTabEntities = tabSuggestionsEnabled
|
||||||
|
? ref.watch(suggestedTabEntitiesProvider(containerId))
|
||||||
|
: EquatableValue(<TabEntity>[]);
|
||||||
|
|
||||||
|
final itemCount =
|
||||||
|
filteredTabEntities.value.length +
|
||||||
|
//Limit to 3 sugegstions for now
|
||||||
|
math.min<int>(suggestedTabEntities.value.length, 3);
|
||||||
|
|
||||||
|
final activeTab = ref.watch(selectedTabProvider);
|
||||||
|
|
||||||
|
final crossAxisCount = useMemoized(() {
|
||||||
|
final calculatedCount = calculateCrossAxisItemCount(
|
||||||
|
screenWidth: screenWidth,
|
||||||
|
horizontalPadding: 4.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return math.max(math.min(calculatedCount, itemCount), 2);
|
||||||
|
}, [screenWidth, itemCount]);
|
||||||
|
|
||||||
|
final itemHeight = useMemoized(
|
||||||
|
() => calculateItemHeight(
|
||||||
|
screenWidth: screenWidth,
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
horizontalPadding: 4.0,
|
||||||
|
mainAxisSpacing: 8.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
),
|
||||||
|
[screenWidth, crossAxisCount],
|
||||||
|
);
|
||||||
|
|
||||||
|
final lastScroll = useRef<String?>(null);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (scrollController.hasClients) {
|
||||||
|
if (lastScroll.value != activeTab) {
|
||||||
|
final index = filteredTabEntities.value.indexWhere(
|
||||||
|
(entity) => entity.tabId == activeTab,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
final offset = (index ~/ 2) * itemHeight;
|
||||||
|
|
||||||
|
if (offset != scrollController.offset) {
|
||||||
|
lastScroll.value = activeTab;
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
scrollController.animateTo(
|
||||||
|
offset,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [filteredTabEntities, activeTab]);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: FadingScroll(
|
||||||
|
fadingSize: 5,
|
||||||
|
controller: scrollController,
|
||||||
|
builder: (context, controller) {
|
||||||
|
return ReorderableBuilder.builder(
|
||||||
|
//Rebuild when cross axis count changes
|
||||||
|
key: ValueKey(crossAxisCount),
|
||||||
|
scrollController: controller,
|
||||||
|
itemCount: itemCount,
|
||||||
|
onDragStarted: (index) {
|
||||||
|
ref.read(willAcceptDropProvider.notifier).clear();
|
||||||
|
},
|
||||||
|
onReorderPositions: (positions) async {
|
||||||
|
assert(positions.length == 1, 'Not ready for multiple reorders');
|
||||||
|
|
||||||
|
final oldIndex = positions.first.oldIndex;
|
||||||
|
final newIndex = positions.first.newIndex;
|
||||||
|
|
||||||
|
final containerRepository = ref.read(
|
||||||
|
containerRepositoryProvider.notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
//Suggestions are at the end and not reorderable, so skip
|
||||||
|
if (oldIndex >= filteredTabEntities.value.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tabId = filteredTabEntities.value[oldIndex].tabId;
|
||||||
|
final containerId = await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.getContainerTabId(tabId);
|
||||||
|
|
||||||
|
final String key;
|
||||||
|
if (newIndex <= 0) {
|
||||||
|
key = await containerRepository.getLeadingOrderKey(containerId);
|
||||||
|
} else if (newIndex >= filteredTabEntities.value.length - 1) {
|
||||||
|
key = await containerRepository.getTrailingOrderKey(
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (newIndex < oldIndex) {
|
||||||
|
key = (await containerRepository.getOrderKeyAfterTab(
|
||||||
|
filteredTabEntities.value[newIndex - 1].tabId,
|
||||||
|
containerId,
|
||||||
|
))!;
|
||||||
|
} else {
|
||||||
|
key = await containerRepository.getOrderKeyBeforeTab(
|
||||||
|
filteredTabEntities.value[newIndex + 1].tabId,
|
||||||
|
containerId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
|
.assignOrderKey(tabId, key);
|
||||||
|
},
|
||||||
|
childBuilder: (itemBuilder) {
|
||||||
|
return GridView.builder(
|
||||||
|
controller: controller,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
//Sync values for itemHeight calculation _calculateItemHeight
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
mainAxisSpacing: 8.0,
|
||||||
|
crossAxisSpacing: 8.0,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
),
|
||||||
|
itemCount: itemCount,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final Widget tab;
|
||||||
|
if (index < filteredTabEntities.value.length) {
|
||||||
|
final entity = filteredTabEntities.value[index];
|
||||||
|
tab = CustomDraggable(
|
||||||
|
key: Key(entity.tabId),
|
||||||
|
data: TabDragData(entity.tabId),
|
||||||
|
child: _TabDraggable(entity: entity, onClose: onClose),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final suggestedIndex =
|
||||||
|
index - filteredTabEntities.value.length;
|
||||||
|
final entity = suggestedTabEntities.value[suggestedIndex];
|
||||||
|
|
||||||
|
tab = CustomDraggable(
|
||||||
|
key: Key('suggested_${entity.tabId}'),
|
||||||
|
child: _TabDraggable(
|
||||||
|
entity: entity,
|
||||||
|
onClose: onClose,
|
||||||
|
suggestedContainerId: ref.watch(
|
||||||
|
selectedContainerProvider,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemBuilder(tab, index);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ViewTabsWidget extends HookConsumerWidget {
|
class ViewTabsWidget extends HookConsumerWidget {
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final DraggableScrollableController? draggableScrollableController;
|
final DraggableScrollableController? draggableScrollableController;
|
||||||
@@ -407,211 +603,7 @@ class ViewTabsWidget extends HookConsumerWidget {
|
|||||||
_TabViewHeader(onClose: onClose, treeViewEnabled: false),
|
_TabViewHeader(onClose: onClose, treeViewEnabled: false),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
body: HookConsumer(
|
body: _TabView(scrollController: scrollController, onClose: onClose),
|
||||||
builder: (context, ref, child) {
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
final containerId = ref.watch(selectedContainerProvider);
|
|
||||||
|
|
||||||
final filteredTabEntities = ref.watch(
|
|
||||||
seamlessFilteredTabEntitiesProvider(
|
|
||||||
searchPartition: TabSearchPartition.preview,
|
|
||||||
// ignore: document_ignores using fast equatable
|
|
||||||
// ignore: provider_parameters
|
|
||||||
containerFilter: ContainerFilterById(
|
|
||||||
containerId: containerId,
|
|
||||||
),
|
|
||||||
groupTrees: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final tabSuggestionsEnabled = ref.watch(
|
|
||||||
tabSuggestionsControllerProvider,
|
|
||||||
);
|
|
||||||
|
|
||||||
final suggestedTabEntities = tabSuggestionsEnabled
|
|
||||||
? ref.watch(suggestedTabEntitiesProvider(containerId))
|
|
||||||
: EquatableValue(<TabEntity>[]);
|
|
||||||
|
|
||||||
final itemCount =
|
|
||||||
filteredTabEntities.value.length +
|
|
||||||
//Limit to 3 sugegstions for now
|
|
||||||
math.min<int>(suggestedTabEntities.value.length, 3);
|
|
||||||
|
|
||||||
final activeTab = ref.watch(selectedTabProvider);
|
|
||||||
|
|
||||||
final crossAxisCount = useMemoized(() {
|
|
||||||
final calculatedCount = calculateCrossAxisItemCount(
|
|
||||||
screenWidth: screenWidth,
|
|
||||||
horizontalPadding: 4.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return math.max(math.min(calculatedCount, itemCount), 2);
|
|
||||||
}, [screenWidth, itemCount]);
|
|
||||||
|
|
||||||
final itemHeight = useMemoized(
|
|
||||||
() => calculateItemHeight(
|
|
||||||
screenWidth: screenWidth,
|
|
||||||
childAspectRatio: 0.75,
|
|
||||||
horizontalPadding: 4.0,
|
|
||||||
mainAxisSpacing: 8.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
crossAxisCount: crossAxisCount,
|
|
||||||
),
|
|
||||||
[screenWidth, crossAxisCount],
|
|
||||||
);
|
|
||||||
|
|
||||||
final lastScroll = useRef<String?>(null);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (scrollController.hasClients) {
|
|
||||||
if (lastScroll.value != activeTab) {
|
|
||||||
final index = filteredTabEntities.value.indexWhere(
|
|
||||||
(entity) => entity.tabId == activeTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index > -1) {
|
|
||||||
final offset = (index ~/ 2) * itemHeight;
|
|
||||||
|
|
||||||
if (offset != scrollController.offset) {
|
|
||||||
lastScroll.value = activeTab;
|
|
||||||
|
|
||||||
unawaited(
|
|
||||||
scrollController.animateTo(
|
|
||||||
offset,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [filteredTabEntities, activeTab]);
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: FadingScroll(
|
|
||||||
fadingSize: 5,
|
|
||||||
controller: scrollController,
|
|
||||||
builder: (context, controller) {
|
|
||||||
return ReorderableBuilder.builder(
|
|
||||||
//Rebuild when cross axis count changes
|
|
||||||
key: ValueKey(crossAxisCount),
|
|
||||||
scrollController: controller,
|
|
||||||
itemCount: itemCount,
|
|
||||||
onDragStarted: (index) {
|
|
||||||
ref.read(willAcceptDropProvider.notifier).clear();
|
|
||||||
},
|
|
||||||
onReorderPositions: (positions) async {
|
|
||||||
assert(
|
|
||||||
positions.length == 1,
|
|
||||||
'Not ready for multiple reorders',
|
|
||||||
);
|
|
||||||
|
|
||||||
final oldIndex = positions.first.oldIndex;
|
|
||||||
final newIndex = positions.first.newIndex;
|
|
||||||
|
|
||||||
final containerRepository = ref.read(
|
|
||||||
containerRepositoryProvider.notifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
//Suggestions are at the end and not reorderable, so skip
|
|
||||||
if (oldIndex >= filteredTabEntities.value.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final tabId = filteredTabEntities.value[oldIndex].tabId;
|
|
||||||
final containerId = await ref
|
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
|
||||||
.getContainerTabId(tabId);
|
|
||||||
|
|
||||||
final String key;
|
|
||||||
if (newIndex <= 0) {
|
|
||||||
key = await containerRepository.getLeadingOrderKey(
|
|
||||||
containerId,
|
|
||||||
);
|
|
||||||
} else if (newIndex >=
|
|
||||||
filteredTabEntities.value.length - 1) {
|
|
||||||
key = await containerRepository.getTrailingOrderKey(
|
|
||||||
containerId,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (newIndex < oldIndex) {
|
|
||||||
key = (await containerRepository
|
|
||||||
.getOrderKeyAfterTab(
|
|
||||||
filteredTabEntities.value[newIndex - 1].tabId,
|
|
||||||
containerId,
|
|
||||||
))!;
|
|
||||||
} else {
|
|
||||||
key = await containerRepository
|
|
||||||
.getOrderKeyBeforeTab(
|
|
||||||
filteredTabEntities.value[newIndex + 1].tabId,
|
|
||||||
containerId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await ref
|
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
|
||||||
.assignOrderKey(tabId, key);
|
|
||||||
},
|
|
||||||
childBuilder: (itemBuilder) {
|
|
||||||
return GridView.builder(
|
|
||||||
controller: controller,
|
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
//Sync values for itemHeight calculation _calculateItemHeight
|
|
||||||
childAspectRatio: 0.75,
|
|
||||||
mainAxisSpacing: 8.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
crossAxisCount: crossAxisCount,
|
|
||||||
),
|
|
||||||
itemCount: itemCount,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final Widget tab;
|
|
||||||
if (index < filteredTabEntities.value.length) {
|
|
||||||
final entity = filteredTabEntities.value[index];
|
|
||||||
tab = CustomDraggable(
|
|
||||||
key: Key(entity.tabId),
|
|
||||||
data: TabDragData(entity.tabId),
|
|
||||||
child: _TabDraggable(
|
|
||||||
entity: entity,
|
|
||||||
onClose: onClose,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final suggestedIndex =
|
|
||||||
index - filteredTabEntities.value.length;
|
|
||||||
final entity =
|
|
||||||
suggestedTabEntities.value[suggestedIndex];
|
|
||||||
|
|
||||||
tab = CustomDraggable(
|
|
||||||
key: Key('suggested_${entity.tabId}'),
|
|
||||||
child: _TabDraggable(
|
|
||||||
entity: entity,
|
|
||||||
onClose: onClose,
|
|
||||||
suggestedContainerId: ref.watch(
|
|
||||||
selectedContainerProvider,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemBuilder(tab, index);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (showNewTabFab)
|
if (showNewTabFab)
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
+99
-105
@@ -38,120 +38,114 @@ class ContainerListScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final containersAsync = ref.watch(watchContainersWithCountProvider);
|
||||||
|
final selectedContainer = ref.watch(selectedContainerProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Containers')),
|
appBar: AppBar(title: const Text('Containers')),
|
||||||
body: HookConsumer(
|
body: Skeletonizer(
|
||||||
builder: (context, ref, child) {
|
enabled: containersAsync.isLoading,
|
||||||
final containersAsync = ref.watch(watchContainersWithCountProvider);
|
child: containersAsync.when(
|
||||||
final selectedContainer = ref.watch(selectedContainerProvider);
|
skipLoadingOnReload: true,
|
||||||
|
data: (containers) => FadingScroll(
|
||||||
|
fadingSize: 25,
|
||||||
|
builder: (context, controller) {
|
||||||
|
return ListView.builder(
|
||||||
|
controller: controller,
|
||||||
|
itemCount: containers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final container = containers[index];
|
||||||
|
return Slidable(
|
||||||
|
key: ValueKey(container.id),
|
||||||
|
startActionPane: ActionPane(
|
||||||
|
motion: const ScrollMotion(),
|
||||||
|
children: [
|
||||||
|
if (container.id != selectedContainer)
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (context) async {
|
||||||
|
final result = await ref
|
||||||
|
.read(selectedContainerProvider.notifier)
|
||||||
|
.setContainerId(container.id);
|
||||||
|
|
||||||
return Skeletonizer(
|
if (context.mounted &&
|
||||||
enabled: containersAsync.isLoading,
|
result ==
|
||||||
child: containersAsync.when(
|
SetContainerResult.successHasProxy) {
|
||||||
skipLoadingOnReload: true,
|
|
||||||
data: (containers) => FadingScroll(
|
|
||||||
fadingSize: 25,
|
|
||||||
builder: (context, controller) {
|
|
||||||
return ListView.builder(
|
|
||||||
controller: controller,
|
|
||||||
itemCount: containers.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final container = containers[index];
|
|
||||||
return Slidable(
|
|
||||||
key: ValueKey(container.id),
|
|
||||||
startActionPane: ActionPane(
|
|
||||||
motion: const ScrollMotion(),
|
|
||||||
children: [
|
|
||||||
if (container.id != selectedContainer)
|
|
||||||
SlidableAction(
|
|
||||||
onPressed: (context) async {
|
|
||||||
final result = await ref
|
|
||||||
.read(selectedContainerProvider.notifier)
|
|
||||||
.setContainerId(container.id);
|
|
||||||
|
|
||||||
if (context.mounted &&
|
|
||||||
result ==
|
|
||||||
SetContainerResult.successHasProxy) {
|
|
||||||
await ref
|
|
||||||
.read(
|
|
||||||
startProxyControllerProvider.notifier,
|
|
||||||
)
|
|
||||||
.maybeStartProxy(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
foregroundColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onPrimaryContainer,
|
|
||||||
backgroundColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.primaryContainer,
|
|
||||||
icon: Icons.check,
|
|
||||||
label: 'Select',
|
|
||||||
)
|
|
||||||
else
|
|
||||||
SlidableAction(
|
|
||||||
onPressed: (context) {
|
|
||||||
ref
|
|
||||||
.read(selectedContainerProvider.notifier)
|
|
||||||
.clearContainer();
|
|
||||||
},
|
|
||||||
foregroundColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onPrimaryContainer,
|
|
||||||
backgroundColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.primaryContainer,
|
|
||||||
icon: Icons.close,
|
|
||||||
label: 'Unselect',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
endActionPane: ActionPane(
|
|
||||||
motion: const ScrollMotion(),
|
|
||||||
children: [
|
|
||||||
SlidableAction(
|
|
||||||
onPressed: (context) async {
|
|
||||||
await ref
|
await ref
|
||||||
.read(containerRepositoryProvider.notifier)
|
.read(startProxyControllerProvider.notifier)
|
||||||
.deleteContainer(container.id);
|
.maybeStartProxy(context);
|
||||||
},
|
}
|
||||||
backgroundColor: Theme.of(
|
},
|
||||||
context,
|
foregroundColor: Theme.of(
|
||||||
).colorScheme.errorContainer,
|
context,
|
||||||
foregroundColor: Theme.of(
|
).colorScheme.onPrimaryContainer,
|
||||||
context,
|
backgroundColor: Theme.of(
|
||||||
).colorScheme.onErrorContainer,
|
context,
|
||||||
icon: Icons.delete,
|
).colorScheme.primaryContainer,
|
||||||
label: 'Delete',
|
icon: Icons.check,
|
||||||
),
|
label: 'Select',
|
||||||
],
|
)
|
||||||
),
|
else
|
||||||
child: ContainerListTile(
|
SlidableAction(
|
||||||
container,
|
onPressed: (context) {
|
||||||
isSelected: container.id == selectedContainer,
|
ref
|
||||||
onTap: () async {
|
.read(selectedContainerProvider.notifier)
|
||||||
await ContainerEditRoute(
|
.clearContainer();
|
||||||
containerData: jsonEncode(container.toJson()),
|
},
|
||||||
).push(context);
|
foregroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onPrimaryContainer,
|
||||||
|
backgroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primaryContainer,
|
||||||
|
icon: Icons.close,
|
||||||
|
label: 'Unselect',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
endActionPane: ActionPane(
|
||||||
|
motion: const ScrollMotion(),
|
||||||
|
children: [
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (context) async {
|
||||||
|
await ref
|
||||||
|
.read(containerRepositoryProvider.notifier)
|
||||||
|
.deleteContainer(container.id);
|
||||||
},
|
},
|
||||||
|
backgroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.errorContainer,
|
||||||
|
foregroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onErrorContainer,
|
||||||
|
icon: Icons.delete,
|
||||||
|
label: 'Delete',
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
|
child: ContainerListTile(
|
||||||
|
container,
|
||||||
|
isSelected: container.id == selectedContainer,
|
||||||
|
onTap: () async {
|
||||||
|
await ContainerEditRoute(
|
||||||
|
containerData: jsonEncode(container.toJson()),
|
||||||
|
).push(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
error: (error, stackTrace) => SizedBox.shrink(),
|
},
|
||||||
loading: () => ListView.builder(
|
),
|
||||||
itemCount: 3,
|
error: (error, stackTrace) => SizedBox.shrink(),
|
||||||
itemBuilder: (context, index) => ContainerListTile(
|
loading: () => ListView.builder(
|
||||||
ContainerData(id: 'null', color: Colors.transparent),
|
itemCount: 3,
|
||||||
onTap: null,
|
itemBuilder: (context, index) => ContainerListTile(
|
||||||
isSelected: false,
|
ContainerData(id: 'null', color: Colors.transparent),
|
||||||
),
|
onTap: null,
|
||||||
),
|
isSelected: false,
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|||||||
+30
-35
@@ -36,47 +36,42 @@ class ContainerSelectionScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final containersAsync = ref.watch(watchContainersWithCountProvider);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Select Container')),
|
appBar: AppBar(title: const Text('Select Container')),
|
||||||
body: HookConsumer(
|
body: Skeletonizer(
|
||||||
builder: (context, ref, child) {
|
enabled: containersAsync.isLoading,
|
||||||
final containersAsync = ref.watch(watchContainersWithCountProvider);
|
child: containersAsync.when(
|
||||||
|
skipLoadingOnReload: true,
|
||||||
return Skeletonizer(
|
data: (containers) => FadingScroll(
|
||||||
enabled: containersAsync.isLoading,
|
fadingSize: 25,
|
||||||
child: containersAsync.when(
|
builder: (context, controller) {
|
||||||
skipLoadingOnReload: true,
|
return ListView.builder(
|
||||||
data: (containers) => FadingScroll(
|
controller: controller,
|
||||||
fadingSize: 25,
|
itemCount: containers.length,
|
||||||
builder: (context, controller) {
|
itemBuilder: (context, index) {
|
||||||
return ListView.builder(
|
final container = containers[index];
|
||||||
controller: controller,
|
return ContainerListTile(
|
||||||
itemCount: containers.length,
|
container,
|
||||||
itemBuilder: (context, index) {
|
isSelected: false,
|
||||||
final container = containers[index];
|
onTap: () {
|
||||||
return ContainerListTile(
|
context.pop<String?>(container.id);
|
||||||
container,
|
|
||||||
isSelected: false,
|
|
||||||
onTap: () {
|
|
||||||
context.pop<String?>(container.id);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
error: (error, stackTrace) => SizedBox.shrink(),
|
},
|
||||||
loading: () => ListView.builder(
|
),
|
||||||
itemCount: 3,
|
error: (error, stackTrace) => SizedBox.shrink(),
|
||||||
itemBuilder: (context, index) => ContainerListTile(
|
loading: () => ListView.builder(
|
||||||
ContainerData(id: 'null', color: Colors.transparent),
|
itemCount: 3,
|
||||||
isSelected: false,
|
itemBuilder: (context, index) => ContainerListTile(
|
||||||
onTap: null,
|
ContainerData(id: 'null', color: Colors.transparent),
|
||||||
),
|
isSelected: false,
|
||||||
),
|
onTap: null,
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|||||||
@@ -37,170 +37,161 @@ class FeedArticleListScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final tags = ref.watch(articleFilterProvider);
|
||||||
|
final articlesAsync = ref.watch(
|
||||||
|
// ignore: provider_parameters
|
||||||
|
filteredArticleListProvider(feedId),
|
||||||
|
);
|
||||||
|
|
||||||
|
final feedTitle = ref.watch(
|
||||||
|
feedDataProvider(
|
||||||
|
feedId,
|
||||||
|
).select((value) => value.value?.title.whenNotEmpty),
|
||||||
|
);
|
||||||
|
|
||||||
|
final focusNode = useFocusNode();
|
||||||
|
final searchTextController = useTextEditingController();
|
||||||
|
|
||||||
|
final hasText = useListenableSelector(
|
||||||
|
searchTextController,
|
||||||
|
() => searchTextController.text.isNotEmpty,
|
||||||
|
);
|
||||||
|
|
||||||
|
useListenableCallback(searchTextController, () {
|
||||||
|
ref
|
||||||
|
.read(filteredArticleListProvider(feedId).notifier)
|
||||||
|
.search(searchTextController.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
final bottomHeight = useMemoized(() {
|
||||||
|
var height = 56.0 + 4.0;
|
||||||
|
|
||||||
|
if (tags.isNotEmpty) {
|
||||||
|
height += 48;
|
||||||
|
}
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}, [tags.isNotEmpty]);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
floatHeaderSlivers: true,
|
floatHeaderSlivers: true,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||||
return [
|
return [
|
||||||
HookConsumer(
|
SliverAppBar(
|
||||||
builder: (context, ref, child) {
|
floating: true,
|
||||||
final tags = ref.watch(articleFilterProvider);
|
title: Text(feedTitle ?? 'Articles'),
|
||||||
final feedTitle = ref.watch(
|
bottom: PreferredSize(
|
||||||
feedDataProvider(
|
preferredSize: Size(double.infinity, bottomHeight),
|
||||||
feedId,
|
child: Padding(
|
||||||
).select((value) => value.value?.title.whenNotEmpty),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
);
|
child: Column(
|
||||||
|
children: [
|
||||||
final focusNode = useFocusNode();
|
TextField(
|
||||||
final searchTextController = useTextEditingController();
|
focusNode: focusNode,
|
||||||
|
controller: searchTextController,
|
||||||
final hasText = useListenableSelector(
|
decoration: InputDecoration(
|
||||||
searchTextController,
|
label: const Text('Search'),
|
||||||
() => searchTextController.text.isNotEmpty,
|
suffixIcon: hasText
|
||||||
);
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
useListenableCallback(searchTextController, () {
|
searchTextController.clear();
|
||||||
ref
|
focusNode.requestFocus();
|
||||||
.read(filteredArticleListProvider(feedId).notifier)
|
},
|
||||||
.search(searchTextController.text);
|
icon: const Icon(Icons.clear),
|
||||||
});
|
)
|
||||||
|
: SpeechToTextButton(
|
||||||
final bottomHeight = useMemoized(() {
|
onTextReceived: (data) {
|
||||||
var height = 56.0 + 4.0;
|
searchTextController.text = data.toString();
|
||||||
|
},
|
||||||
if (tags.isNotEmpty) {
|
),
|
||||||
height += 48;
|
),
|
||||||
}
|
|
||||||
|
|
||||||
return height;
|
|
||||||
}, [tags.isNotEmpty]);
|
|
||||||
|
|
||||||
return SliverAppBar(
|
|
||||||
floating: true,
|
|
||||||
title: Text(feedTitle ?? 'Articles'),
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: Size(double.infinity, bottomHeight),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextField(
|
|
||||||
focusNode: focusNode,
|
|
||||||
controller: searchTextController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
label: const Text('Search'),
|
|
||||||
suffixIcon: hasText
|
|
||||||
? IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
searchTextController.clear();
|
|
||||||
focusNode.requestFocus();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
)
|
|
||||||
: SpeechToTextButton(
|
|
||||||
onTextReceived: (data) {
|
|
||||||
searchTextController.text = data
|
|
||||||
.toString();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
if (tags.isNotEmpty)
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 48,
|
|
||||||
child: FadingScroll(
|
|
||||||
fadingSize: 15,
|
|
||||||
builder: (context, controller) {
|
|
||||||
return ListView(
|
|
||||||
controller: controller,
|
|
||||||
shrinkWrap: true,
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
children: tags
|
|
||||||
.map(
|
|
||||||
(tag) => Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
right: 8.0,
|
|
||||||
),
|
|
||||||
child: FilterChip(
|
|
||||||
label: Text(tag),
|
|
||||||
showCheckmark: false,
|
|
||||||
selected: true,
|
|
||||||
onSelected: (value) {},
|
|
||||||
onDeleted: () {
|
|
||||||
ref
|
|
||||||
.read(
|
|
||||||
articleFilterProvider
|
|
||||||
.notifier,
|
|
||||||
)
|
|
||||||
.removeTag(tag);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
|
if (tags.isNotEmpty)
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 48,
|
||||||
|
child: FadingScroll(
|
||||||
|
fadingSize: 15,
|
||||||
|
builder: (context, controller) {
|
||||||
|
return ListView(
|
||||||
|
controller: controller,
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children: tags
|
||||||
|
.map(
|
||||||
|
(tag) => Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 8.0,
|
||||||
|
),
|
||||||
|
child: FilterChip(
|
||||||
|
label: Text(tag),
|
||||||
|
showCheckmark: false,
|
||||||
|
selected: true,
|
||||||
|
onSelected: (value) {},
|
||||||
|
onDeleted: () {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
articleFilterProvider
|
||||||
|
.notifier,
|
||||||
|
)
|
||||||
|
.removeTag(tag);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
body: Consumer(
|
body: articlesAsync.when(
|
||||||
builder: (context, ref, child) {
|
skipLoadingOnReload: true,
|
||||||
final articlesAsync = ref.watch(
|
data: (articles) {
|
||||||
// ignore: provider_parameters
|
return RefreshIndicator(
|
||||||
filteredArticleListProvider(feedId),
|
onRefresh: () async {
|
||||||
);
|
if (feedId != null) {
|
||||||
|
await ref
|
||||||
return articlesAsync.when(
|
.read(fetchArticlesControllerProvider.notifier)
|
||||||
skipLoadingOnReload: true,
|
.fetchFeedArticles(feedId!);
|
||||||
data: (articles) {
|
} else {
|
||||||
return RefreshIndicator(
|
await ref
|
||||||
onRefresh: () async {
|
.read(fetchArticlesControllerProvider.notifier)
|
||||||
if (feedId != null) {
|
.fetchAllArticles();
|
||||||
await ref
|
}
|
||||||
.read(fetchArticlesControllerProvider.notifier)
|
|
||||||
.fetchFeedArticles(feedId!);
|
|
||||||
} else {
|
|
||||||
await ref
|
|
||||||
.read(fetchArticlesControllerProvider.notifier)
|
|
||||||
.fetchAllArticles();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: MediaQuery.removePadding(
|
|
||||||
removeTop: true,
|
|
||||||
context: context,
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
itemCount: articles.length,
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
final article = articles[i];
|
|
||||||
return FeedArticleCard(
|
|
||||||
key: ValueKey(article.id),
|
|
||||||
article: article,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
error: (error, stackTrace) => Center(
|
child: MediaQuery.removePadding(
|
||||||
child: FailureWidget(
|
removeTop: true,
|
||||||
title: 'Failed to load Articles',
|
context: context,
|
||||||
exception: error,
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: articles.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final article = articles[i];
|
||||||
|
return FeedArticleCard(
|
||||||
|
key: ValueKey(article.id),
|
||||||
|
article: article,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
loading: () => const SizedBox.shrink(),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
error: (error, stackTrace) => Center(
|
||||||
|
child: FailureWidget(
|
||||||
|
title: 'Failed to load Articles',
|
||||||
|
exception: error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user