diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 134095ae..171ad408 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -955,6 +955,58 @@ "@secondsShortForm": { "description": "Short form for the word seconds" }, + "videoFilterApplyDateToFilter": "Filter videos on given times", + "@videoFilterApplyDateToFilter": { + "description": "Label for switch to allow user to customize video filter and set days of week and time to them" + }, + "videoFilterDayOfWeek": "Select days to apply filters", + "@videoFilterDayOfWeek": { + "description": "Title for day selection for the filter" + }, + "videoFilterDayOfWeekDescription": "You can selectively choose days of the week and time to which the filters apply to, for example, avoid sport events spoilers.", + "@videoFilterDayOfWeekDescription": { + "description": "" + }, + "videoFilterStartTime": "Start time", + "@videoFilterStartTime": { + "description": "Title for filter start time" + }, + "videoFilterEndTime": "End time", + "@videoFilterEndTime": { + "description": "Title for filter end time" + }, + "videoFilterAppliedOn": "Applied on {selectedDays}", + "@videoFilterAppliedOn": { + "description": "Readable text on when the filter should apply", + "placeholders": { + "selectedDays": { + "type": "String", + "example": "Monday, Wednesday, Friday" + } + } + }, + "from": "From", + "@from": { + "description": "From word (as in 'From xx To xx')" + }, + "to": "To", + "@to": { + "description": "To word as in 'From xx To xx')" + }, + "videoFilterTimeOfDayFromTo": "From {from} to {to}", + "@videoFilterTimeOfDayFromTo": { + "description": "Time of day range", + "placeholders": { + "from": { + "type": "String", + "example": "3:00 AM" + }, + "to": { + "type": "String", + "example": "5:00 PM" + } + } + }, "history": "History", "@history": { "description": "User view history label" diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json index 6e43c5d2..960bdc0e 100644 --- a/lib/objectbox-model.json +++ b/lib/objectbox-model.json @@ -158,7 +158,7 @@ }, { "id": "6:8304874620604193998", - "lastPropertyId": "8:6020474727686624632", + "lastPropertyId": "11:8416925878752879022", "name": "VideoFilter", "properties": [ { @@ -196,6 +196,21 @@ "id": "8:6020474727686624632", "name": "hideFromFeed", "type": 1 + }, + { + "id": "9:385259419700560741", + "name": "daysOfWeek", + "type": 27 + }, + { + "id": "10:4391992064156904605", + "name": "startTime", + "type": 9 + }, + { + "id": "11:8416925878752879022", + "name": "endTime", + "type": 9 } ], "relations": [] diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart index 46798317..655050f9 100644 --- a/lib/objectbox.g.dart +++ b/lib/objectbox.g.dart @@ -179,7 +179,7 @@ final _entities = [ ModelEntity( id: const IdUid(6, 8304874620604193998), name: 'VideoFilter', - lastPropertyId: const IdUid(8, 6020474727686624632), + lastPropertyId: const IdUid(11, 8416925878752879022), flags: 0, properties: [ ModelProperty( @@ -216,6 +216,21 @@ final _entities = [ id: const IdUid(8, 6020474727686624632), name: 'hideFromFeed', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(9, 385259419700560741), + name: 'daysOfWeek', + type: 27, + flags: 0), + ModelProperty( + id: const IdUid(10, 4391992064156904605), + name: 'startTime', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(11, 8416925878752879022), + name: 'endTime', + type: 9, flags: 0) ], relations: [], @@ -598,7 +613,10 @@ ModelDefinition getObjectBoxModel() { final dbOperationOffset = object.dbOperation == null ? null : fbb.writeString(object.dbOperation!); - fbb.startTable(9); + final daysOfWeekOffset = fbb.writeListInt64(object.daysOfWeek); + final startTimeOffset = fbb.writeString(object.startTime); + final endTimeOffset = fbb.writeString(object.endTime); + fbb.startTable(12); fbb.addInt64(0, object.id); fbb.addOffset(1, channelIdOffset); fbb.addOffset(2, valueOffset); @@ -606,6 +624,9 @@ ModelDefinition getObjectBoxModel() { fbb.addOffset(4, dbOperationOffset); fbb.addBool(6, object.filterAll); fbb.addBool(7, object.hideFromFeed); + fbb.addOffset(8, daysOfWeekOffset); + fbb.addOffset(9, startTimeOffset); + fbb.addOffset(10, endTimeOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -626,7 +647,14 @@ ModelDefinition getObjectBoxModel() { ..filterAll = const fb.BoolReader().vTableGet(buffer, rootOffset, 16, false) ..hideFromFeed = - const fb.BoolReader().vTableGet(buffer, rootOffset, 18, false); + const fb.BoolReader().vTableGet(buffer, rootOffset, 18, false) + ..daysOfWeek = + const fb.ListReader(fb.Int64Reader(), lazy: false) + .vTableGet(buffer, rootOffset, 20, []) + ..startTime = const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 22, '') + ..endTime = const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 24, ''); return object; }), @@ -900,6 +928,18 @@ class VideoFilter_ { /// see [VideoFilter.hideFromFeed] static final hideFromFeed = QueryBooleanProperty(_entities[5].properties[6]); + + /// see [VideoFilter.daysOfWeek] + static final daysOfWeek = + QueryIntegerVectorProperty(_entities[5].properties[7]); + + /// see [VideoFilter.startTime] + static final startTime = + QueryStringProperty(_entities[5].properties[8]); + + /// see [VideoFilter.endTime] + static final endTime = + QueryStringProperty(_entities[5].properties[9]); } /// [DownloadedVideo] entity fields to define ObjectBox queries. diff --git a/lib/settings/models/db/video_filter.dart b/lib/settings/models/db/video_filter.dart index 66fbe4f7..e1c4df8c 100644 --- a/lib/settings/models/db/video_filter.dart +++ b/lib/settings/models/db/video_filter.dart @@ -1,6 +1,8 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:invidious/globals.dart'; +import 'package:invidious/utils.dart'; import 'package:invidious/videos/models/base_video.dart'; import 'package:logging/logging.dart'; import 'package:objectbox/objectbox.dart'; @@ -9,6 +11,10 @@ import '../../../utils/models/pair.dart'; final log = Logger('Video Filter DB'); +const defaultStartTime = "00:00:00"; +const defaultEndTime = "23:59:59"; +const wholeWeek = [1, 2, 3, 4, 5, 6, 7]; + enum FilterType { title, channelName, @@ -69,6 +75,10 @@ class VideoFilter { return type?.name ?? ''; } + List daysOfWeek = wholeWeek; + String startTime = defaultStartTime; + String endTime = defaultEndTime; + set dbType(String? value) { type = FilterType.values.where((element) => element.name == value).firstOrNull; } @@ -97,10 +107,14 @@ class VideoFilter { log.fine('filtering videos, we have ${filters.length} filters'); +/* videos = await Future.wait(videos?.map((v) { return compute((message) => _innerFilterVideo(message.first, message.last), Couple(v, filters)); }).toList() ?? []); +*/ + + videos = videos?.map((v) => _innerFilterVideo(v, filters)).toList() ?? []; return videos; } @@ -115,21 +129,54 @@ class VideoFilter { // Channel hide all if (channelId != null && filterAll == true && videoChannel == channelId) { log.fine('Video filtered because hide all == $filterAll, video channel id: ${videoChannel}, channel id ${channelId}'); - return true; + return !isTimeAllowed(); } + bool filter = false; + switch (type) { // string base operation case FilterType.title: - return filterVideoStringOperation(video.title); + filter = filterVideoStringOperation(video.title); case FilterType.channelName: - return video.author != null ? filterVideoStringOperation(video.author!) : true; + filter = video.author != null ? filterVideoStringOperation(video.author!) : true; // int base operation case FilterType.length: - return filterVideoNumberOperation(video.lengthSeconds); + filter = filterVideoNumberOperation(video.lengthSeconds); default: - return false; + filter = false; + } + + if (!filter) { + return false; + } + //if we need to filter, we need to check the time + return filter && !isTimeAllowed(); + } + + bool isTimeAllowed() { + var now = DateTime.now(); + + var daysToTest = daysOfWeek.isEmpty ? wholeWeek : daysOfWeek; + bool isDayAllowed = !daysToTest.contains(now.weekday); + + if (isDayAllowed) { + print("Filter daysOfWeek ${daysOfWeek}, now day of week: ${now.weekday}"); + return true; } + + List safeStartTime = (startTime.isEmpty ? defaultStartTime : startTime).split(":"); + List safeEndTime = (endTime.isEmpty ? defaultEndTime : endTime).split(":"); + + DateTime startDateTime = DateTime.now().copyWith(hour: int.parse(safeStartTime[0]), minute: int.parse(safeStartTime[1]), second: int.parse(safeStartTime[2])); + DateTime endDateTime = DateTime.now().copyWith(hour: int.parse(safeEndTime[0]), minute: int.parse(safeEndTime[1]), second: int.parse(safeEndTime[2])); + + // we only allow the video if current time is outside current time range + bool isTimeAllowed = now.isBefore(startDateTime) || now.isAfter(endDateTime); + + print("Filter daysOfWeek ${daysOfWeek}, now day of week: ${now.weekday}, Filter from ${startDateTime} to ${endDateTime} current time ${now} "); + + return isTimeAllowed; } bool filterVideoNumberOperation(int numberToCompare) { @@ -160,15 +207,54 @@ class VideoFilter { } } - String localizedLabel(AppLocalizations locals) { + String localizedLabel(AppLocalizations locals, BuildContext context) { + String str = ""; if (filterAll) { - return locals.videoFilterWholeChannel(hideFromFeed ? locals.videoFilterHideLabel : locals.videoFilterFilterLabel); + str = locals.videoFilterWholeChannel(hideFromFeed ? locals.videoFilterHideLabel : locals.videoFilterFilterLabel); } else if (type != null && operation != null) { log.fine("Filter type $hideFromFeed"); - return locals.videoFilterDescriptionString(hideFromFeed ? locals.videoFilterHideLabel : locals.videoFilterFilterLabel, FilterType.localizedType(type!, locals).toLowerCase(), + str = locals.videoFilterDescriptionString(hideFromFeed ? locals.videoFilterHideLabel : locals.videoFilterFilterLabel, FilterType.localizedType(type!, locals).toLowerCase(), FilterOperation.localizedLabel(operation!, locals).toLowerCase(), value ?? ''); + } + + String daysOfWeek = localizedDaysOfWeek(locals); + if (daysOfWeek.isNotEmpty) { + if (str.isNotEmpty) { + str += "\n$daysOfWeek"; + } else { + str = daysOfWeek; + } + } + + String localizedTime = localizedTimes(locals, context); + if (localizedTime.isNotEmpty) { + if (str.isNotEmpty) { + str += "\n$localizedTime"; + } else { + str = localizedTime; + } + } + + return str; + } + + String localizedDaysOfWeek(AppLocalizations locals) { + if (daysOfWeek.isNotEmpty && daysOfWeek.length != 7) { + daysOfWeek.sort(); + return locals.videoFilterAppliedOn(daysOfWeek.map((e) => getWeekdayName(e)).join(", ")); } else { - return ""; + return ''; } } + + String localizedTimes(AppLocalizations locals, BuildContext context) { + String str = ''; + if (startTime != defaultStartTime || endTime != defaultEndTime) { + var start = timeStringToTimeOfDay(startTime); + var end = timeStringToTimeOfDay(endTime); + str = locals.videoFilterTimeOfDayFromTo(start.format(context), end.format(context)); + } + + return str; + } } diff --git a/lib/settings/states/video_filter_edit.dart b/lib/settings/states/video_filter_edit.dart index f1ddfd66..1bd81886 100644 --- a/lib/settings/states/video_filter_edit.dart +++ b/lib/settings/states/video_filter_edit.dart @@ -35,6 +35,16 @@ class VideoFilterEditCubit extends Cubit { void ensureFilter() { state.filter ??= VideoFilter(value: ""); + if ((state.filter?.daysOfWeek ?? wholeWeek).isEmpty) { + state.filter?.daysOfWeek = wholeWeek; + } + + if ((state.filter?.startTime ?? '').isEmpty) { + state.filter?.startTime = defaultStartTime; + } + if ((state.filter?.endTime ?? '').isEmpty) { + state.filter?.endTime = defaultEndTime; + } } void setType(FilterType? value) { @@ -127,11 +137,58 @@ class VideoFilterEditCubit extends Cubit { emit(state); } + set showDateSettings(bool show) { + var state = this.state.copyWith(); + state.showDateSettings = show; + if (!show) { + state.filter?.daysOfWeek = wholeWeek; + state.filter?.startTime = defaultStartTime; + state.filter?.endTime = defaultEndTime; + } + emit(state); + } + void hideOnFilteredChanged(bool value) { var state = this.state.copyWith(); state.filter?.hideFromFeed = value; emit(state); } + + toggleDay(int e) { + var days = List.of(state.filter?.daysOfWeek ?? wholeWeek); + if (days.contains(e)) { + if (days.length >= 2) days.remove(e); + } else { + days.add(e); + } + var filter = state.filter; + filter?.daysOfWeek = days; + emit(state.copyWith(filter: filter)); + } + + bool get showDateSettings => + state.showDateSettings || + (state.filter?.daysOfWeek.length ?? wholeWeek.length) != wholeWeek.length || + (state.filter?.startTime ?? defaultStartTime) != defaultStartTime || + (state.filter?.endTime ?? defaultEndTime) != defaultEndTime; + + setStartTime(String newTime) { + var state = this.state.copyWith(); + var comparison = newTime.compareTo(state.filter?.endTime ?? defaultEndTime); + if (comparison < 0) { + state.filter?.startTime = newTime; + emit(state); + } + } + + setEndTime(String newTime) { + var state = this.state.copyWith(); + var comparison = newTime.compareTo(state.filter?.startTime ?? defaultStartTime); + if (comparison > 0) { + state.filter?.endTime = newTime; + emit(state); + } + } } @CopyWith(constructor: "_") @@ -140,6 +197,7 @@ class VideoFilterEditState { int searchPage; Channel? channel; List channelResults; + bool showDateSettings = false; TextEditingController valueController; @@ -147,5 +205,5 @@ class VideoFilterEditState { : channelResults = channelResults ?? [], valueController = valueController ?? TextEditingController(text: filter?.value ?? ''); - VideoFilterEditState._(this.filter, this.searchPage, this.channel, this.channelResults, this.valueController); + VideoFilterEditState._(this.filter, this.searchPage, this.channel, this.channelResults, this.valueController, this.showDateSettings); } diff --git a/lib/settings/states/video_filter_edit.g.dart b/lib/settings/states/video_filter_edit.g.dart index 718cdecd..ec346379 100644 --- a/lib/settings/states/video_filter_edit.g.dart +++ b/lib/settings/states/video_filter_edit.g.dart @@ -17,6 +17,8 @@ abstract class _$VideoFilterEditStateCWProxy { VideoFilterEditState valueController(TextEditingController valueController); + VideoFilterEditState showDateSettings(bool showDateSettings); + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `VideoFilterEditState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// /// Usage @@ -29,6 +31,7 @@ abstract class _$VideoFilterEditStateCWProxy { Channel? channel, List? channelResults, TextEditingController? valueController, + bool? showDateSettings, }); } @@ -57,6 +60,10 @@ class _$VideoFilterEditStateCWProxyImpl VideoFilterEditState valueController(TextEditingController valueController) => this(valueController: valueController); + @override + VideoFilterEditState showDateSettings(bool showDateSettings) => + this(showDateSettings: showDateSettings); + @override /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `VideoFilterEditState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. @@ -71,6 +78,7 @@ class _$VideoFilterEditStateCWProxyImpl Object? channel = const $CopyWithPlaceholder(), Object? channelResults = const $CopyWithPlaceholder(), Object? valueController = const $CopyWithPlaceholder(), + Object? showDateSettings = const $CopyWithPlaceholder(), }) { return VideoFilterEditState._( filter == const $CopyWithPlaceholder() @@ -93,6 +101,11 @@ class _$VideoFilterEditStateCWProxyImpl ? _value.valueController // ignore: cast_nullable_to_non_nullable : valueController as TextEditingController, + showDateSettings == const $CopyWithPlaceholder() || + showDateSettings == null + ? _value.showDateSettings + // ignore: cast_nullable_to_non_nullable + : showDateSettings as bool, ); } } diff --git a/lib/settings/views/components/video_filter_item.dart b/lib/settings/views/components/video_filter_item.dart index 277b1603..ac153e5a 100644 --- a/lib/settings/views/components/video_filter_item.dart +++ b/lib/settings/views/components/video_filter_item.dart @@ -19,7 +19,7 @@ class VideoFilterItem extends StatelessWidget { decoration: BoxDecoration(color: colors.secondaryContainer, borderRadius: BorderRadius.circular(10)), child: Padding( padding: const EdgeInsets.all(8.0), - child: Text(filter.localizedLabel(locals)), + child: Text(filter.localizedLabel(locals, context)), )), ); } diff --git a/lib/settings/views/screens/video_filter_setup.dart b/lib/settings/views/screens/video_filter_setup.dart index e273f1e8..e6034cd6 100644 --- a/lib/settings/views/screens/video_filter_setup.dart +++ b/lib/settings/views/screens/video_filter_setup.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:invidious/globals.dart'; import 'package:invidious/settings/states/video_filter_edit.dart'; +import 'package:invidious/utils.dart'; import 'package:invidious/utils/views/components/select_list_dialog.dart'; import '../../../channels/models/channel.dart'; @@ -71,6 +74,17 @@ class VideoFilterSetup extends StatelessWidget { itemBuilder: (value, selected) => Text(value.author), asyncSearch: (filter) => cubit.searchChannel(filter ?? ''), onSelect: (value) => cubit.selectChannel(value), title: locals.channel); } + selectTime(BuildContext context, String initialTime, Function(String newTime) onNewTime) async { + var split = initialTime.split(":"); + if (split.length == 3) { + TimeOfDay? selectedTime = await showTimePicker(context: context, initialTime: timeStringToTimeOfDay(initialTime)); + if (selectedTime != null) { + String newTime = '${selectedTime.hour.toString().padLeft(2, "0")}:${selectedTime.minute.toString().padLeft(2, "0")}:${split[2]}'; + onNewTime(newTime); + } + } + } + @override Widget build(BuildContext context) { var locals = AppLocalizations.of(context)!; @@ -81,6 +95,8 @@ class VideoFilterSetup extends StatelessWidget { create: (context) => VideoFilterEditCubit(VideoFilterEditState(filter: filter)), child: BlocBuilder(builder: (context, _) { var cubit = context.read(); + print(_.filter?.daysOfWeek); + print(_.filter?.startTime); return Scaffold( appBar: AppBar( backgroundColor: colors.background, @@ -145,6 +161,72 @@ class VideoFilterSetup extends StatelessWidget { visible: _.filter?.channelId != null, child: SwitchListTile(title: Text(locals.videoFilterHideAllFromChannel), value: _.filter?.filterAll ?? false, onChanged: cubit.channelHideAll)), ...getFilterWidgets(context), + SwitchListTile( + title: Text(locals.videoFilterDayOfWeek), + subtitle: Text( + locals.videoFilterDayOfWeekDescription, + style: textTheme.bodySmall?.copyWith(color: colors.secondary), + ), + value: cubit.showDateSettings, + onChanged: (value) => cubit.showDateSettings = value), + AnimatedCrossFade( + firstChild: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: wholeWeek.map((e) { + String day = getWeekdayName(e).substring(0, 1); + return GestureDetector( + onTap: () => cubit.toggleDay(e), + child: AnimatedContainer( + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.symmetric(horizontal: 8), + width: 30, + height: 30, + alignment: Alignment.center, + decoration: BoxDecoration(shape: BoxShape.circle, color: (_.filter?.daysOfWeek.contains(e) ?? false) ? colors.primaryContainer : colors.secondaryContainer), + duration: animationDuration, + curve: Curves.easeInOutQuad, + child: Text( + day, + style: textTheme.bodySmall, + ), + ), + ); + }).toList()), + SizedBox( + height: 4, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('${locals.from}:'), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: FilledButton.tonal( + onPressed: () => selectTime(context, _.filter?.startTime ?? defaultStartTime, cubit.setStartTime), + child: Text(timeStringToTimeOfDay(_.filter?.startTime ?? defaultStartTime).format(context))), + ), + Text('${locals.to}:'), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: FilledButton.tonal( + onPressed: () => selectTime(context, _.filter?.endTime ?? defaultEndTime, cubit.setEndTime), + child: Text(timeStringToTimeOfDay(_.filter?.endTime ?? defaultEndTime).format(context))), + ), + ], + ) + ], + ), + secondChild: const SizedBox.shrink(), + crossFadeState: cubit.showDateSettings ? CrossFadeState.showFirst : CrossFadeState.showSecond, + duration: animationDuration, + sizeCurve: Curves.easeInOutQuad, + firstCurve: Curves.easeInOutQuad, + secondCurve: Curves.easeInOutQuad, + ).animate().slideY(duration: animationDuration, curve: Curves.easeInOutQuad).fadeIn(duration: animationDuration), SwitchListTile( title: Text(locals.videoFilterHide), subtitle: Text( @@ -158,7 +240,7 @@ class VideoFilterSetup extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16.0), child: Text( - _.filter?.localizedLabel(locals) ?? '', + _.filter?.localizedLabel(locals, context) ?? '', style: TextStyle(color: colors.primary), ), )), diff --git a/lib/utils.dart b/lib/utils.dart index 25dbd013..80bcda64 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -282,3 +282,23 @@ SystemUiOverlayStyle getUiOverlayStyle(BuildContext context) { } List filteredVideos(List videos) => videos.where((element) => !element.filterHide).toList(); + +String getWeekdayName(int weekday) { + final DateTime now = DateTime.now().toLocal(); + final int diff = now.weekday - weekday; // weekday is our 1-7 ISO value + var udpatedDt; + if (diff > 0) { + udpatedDt = now.subtract(Duration(days: diff)); + } else if (diff == 0) { + udpatedDt = now; + } else { + udpatedDt = now.add(Duration(days: diff * -1)); + } + final String weekdayName = DateFormat('EEEE').format(udpatedDt); + return weekdayName; +} + +TimeOfDay timeStringToTimeOfDay(String time) { + var split = time.split(":"); + return TimeOfDay(hour: int.parse(split[0]), minute: int.parse(split[1])); +} diff --git a/lib/videos/views/components/video_in_list.dart b/lib/videos/views/components/video_in_list.dart index a3788e47..563fd622 100644 --- a/lib/videos/views/components/video_in_list.dart +++ b/lib/videos/views/components/video_in_list.dart @@ -83,27 +83,30 @@ class VideoListItem extends StatelessWidget { size: 10, color: colorScheme.secondary, ) - : Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - locals.videoFiltered, - style: filterStyle, - ), - ...video!.matchedFilters - .map((e) => Text( - e.localizedLabel(locals), - style: filterStyle, - )) - .toList(growable: false), - Padding( - padding: const EdgeInsets.only(top: 16.0), - child: Text( - locals.videoFilterTapToReveal, + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + locals.videoFiltered, style: filterStyle, ), - ) - ], + ...video!.matchedFilters + .map((e) => Text( + e.localizedLabel(locals, context), + style: filterStyle, + )) + .toList(growable: false), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + locals.videoFilterTapToReveal, + style: filterStyle, + ), + ) + ], + ), ), ), ) diff --git a/pubspec.lock b/pubspec.lock index 2867f963..c6e91328 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -254,10 +254,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.17.1" convert: dependency: transitive description: @@ -628,10 +628,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.18.0" io: dependency: transitive description: @@ -725,18 +725,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -1090,10 +1090,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" sqflite: dependency: transitive description: @@ -1170,26 +1170,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" url: "https://pub.dev" source: hosted - version: "1.24.3" + version: "1.24.1" test_api: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.5.1" test_core: dependency: transitive description: name: test_core - sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "0.5.1" timing: dependency: transitive description: @@ -1330,10 +1330,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f + sha256: f6deed8ed625c52864792459709183da231ebf66ff0cf09e69b573227c377efe url: "https://pub.dev" source: hosted - version: "11.7.1" + version: "11.3.0" wakelock: dependency: "direct main" description: @@ -1383,14 +1383,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1456,5 +1448,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0"