Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ jobs:
ls -R /tmp/artifacts/final

- name: Upload to S3
id: upload_s3
timeout-minutes: 5
continue-on-error: true
uses: Robert-Stackflow/upload-s3-action@master
with:
endpoint: ${{ secrets.AWS_ENDPOINT }}
Expand All @@ -179,6 +182,11 @@ jobs:
aws_bucket: ${{ secrets.AWS_BUCKET }}
source_dir: /tmp/artifacts/final
destination_dir: Loftify/${{ steps.get_version.outputs.version }}
- name: Report S3 upload failure
if: steps.upload_s3.outcome == 'failure'
run: |
echo "::warning title=S3 upload failed::Continuing with GitHub Release upload."
echo "S3 upload failed; GitHub Release upload will still run." >> "$GITHUB_STEP_SUMMARY"
- name: Upload to release
uses: Robert-Stackflow/release-action@master
with:
Expand Down
7 changes: 7 additions & 0 deletions lib/Screens/Post/collection_detail_screen.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:awesome_chewie/awesome_chewie.dart';
import 'package:blur/blur.dart';
import 'package:flutter/material.dart' hide AnimatedSlide;
Expand All @@ -14,6 +16,7 @@ import '../../Models/post_detail_response.dart';
import '../../Models/recommend_response.dart';
import '../../Screens/Download/batch_download_screen.dart';
import '../../Utils/enums.dart';
import '../../Utils/content_order_preference.dart';
import '../../Widgets/PostItem/common_info_post_item_builder.dart';
import '../../Widgets/PostItem/general_post_item.dart';
import '../../Widgets/PostItem/loftify_post_archive_grid.dart';
Expand Down Expand Up @@ -165,6 +168,7 @@ class CollectionDetailScreenState
@override
void initState() {
super.initState();
isOldest = ContentOrderPreference.read(ContentOrderType.collection);
_fetchData(refresh: true);
_fetchIncantation();
}
Expand Down Expand Up @@ -335,10 +339,13 @@ class CollectionDetailScreenState
fontSizeDelta: 1,
color: Theme.of(context).textTheme.labelMedium?.color,
onTap: () {
if (loading) return;
HapticFeedback.mediumImpact();
setState(() {
isOldest = !isOldest;
});
unawaited(ContentOrderPreference.write(
ContentOrderType.collection, isOldest));
_fetchData(refresh: true, showLoading: true);
},
),
Expand Down
7 changes: 7 additions & 0 deletions lib/Screens/Post/grain_detail_screen.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:awesome_chewie/awesome_chewie.dart';
import 'package:blur/blur.dart';
import 'package:flutter/material.dart' hide AnimatedSlide;
Expand All @@ -13,6 +15,7 @@ import 'package:loftify/Widgets/PostItem/loftify_post_archive_grid.dart';

import '../../Models/history_response.dart';
import '../../Models/download_task.dart';
import '../../Utils/content_order_preference.dart';
import '../../Screens/Download/batch_download_screen.dart';
import '../../Utils/post_sequence_source.dart';
import '../../Widgets/PostDetail/detail_bottom_bar.dart';
Expand Down Expand Up @@ -156,6 +159,7 @@ class GrainDetailScreenState extends BaseDynamicState<GrainDetailScreen>
@override
void initState() {
super.initState();
isOldest = ContentOrderPreference.read(ContentOrderType.grain);
_postSequenceSource = PostSequenceSource(
loadMore: () async {
await _fetchData();
Expand Down Expand Up @@ -399,10 +403,13 @@ class GrainDetailScreenState extends BaseDynamicState<GrainDetailScreen>
fontSizeDelta: 1,
color: Theme.of(context).textTheme.labelMedium?.color,
onTap: () {
if (loading) return;
HapticFeedback.mediumImpact();
setState(() {
isOldest = !isOldest;
});
unawaited(ContentOrderPreference.write(
ContentOrderType.grain, isOldest));
_fetchData(refresh: true, showLoading: true);
},
),
Expand Down
24 changes: 24 additions & 0 deletions lib/Utils/content_order_preference.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:hive/hive.dart';

import 'hive_util.dart';

enum ContentOrderType { collection, grain }

/// The collection detail page and its directory sheet share one preference.
/// Grain order is independent because it uses a separate content type.
class ContentOrderPreference {
const ContentOrderPreference._();

static String _key(ContentOrderType type) => switch (type) {
ContentOrderType.collection => HiveUtil.collectionOldestFirstKey,
ContentOrderType.grain => HiveUtil.grainOldestFirstKey,
};

static bool read(ContentOrderType type) {
final value = Hive.box(HiveUtil.settingsBox).get(_key(type));
return value is bool ? value : false;
}

static Future<void> write(ContentOrderType type, bool oldestFirst) =>
Hive.box(HiveUtil.settingsBox).put(_key(type), oldestFirst);
}
2 changes: 2 additions & 0 deletions lib/Utils/hive_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class HiveUtil {
static const String cookieKey = "cookieKey";
static const String customAvatarBoxKey = "customAvatarBox";
static const String searchHistoryKey = "searchHistory";
static const String collectionOldestFirstKey = "collectionOldestFirst";
static const String grainOldestFirstKey = "grainOldestFirst";

//General
static const String localeKey = "locale";
Expand Down
19 changes: 14 additions & 5 deletions lib/Widgets/BottomSheet/collection_bottom_sheet.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:awesome_chewie/awesome_chewie.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand All @@ -7,6 +9,7 @@ import 'package:loftify/Models/recommend_response.dart';
import 'package:loftify/Screens/Post/collection_detail_screen.dart';

import '../../Models/history_response.dart';
import '../../Utils/content_order_preference.dart';
import '../../Theme/loftify_design_theme.dart';
import '../../l10n/l10n.dart';
import '../Design/loftify_controls.dart';
Expand Down Expand Up @@ -53,6 +56,7 @@ class CollectionBottomSheetState extends State<CollectionBottomSheet> {
void initState() {
super.initState();
subscribed = widget.postCollection.subscribed;
isOldest = ContentOrderPreference.read(ContentOrderType.collection);
}

@override
Expand Down Expand Up @@ -316,15 +320,20 @@ class CollectionBottomSheetState extends State<CollectionBottomSheet> {
fontSizeDelta: 1,
color: Theme.of(context).textTheme.labelMedium?.color,
onTap: () {
if (loading) return;
HapticFeedback.mediumImpact();
if (_scrollController.hasClients) {
_scrollController.jumpTo(0);
}
setState(() {
isOldest = !isOldest;
posts.clear();
_archiveDataList.clear();
bottomNoMore = false;
isInited = false;
});
_scrollController.animateTo(0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut);
bottomNoMore = false;
isInited = false;
unawaited(ContentOrderPreference.write(
ContentOrderType.collection, isOldest));
_refreshController.resetHeader();
_refreshController.resetFooter();
_onRefresh(showLoading: true);
Expand Down
54 changes: 54 additions & 0 deletions test/content_order_preference_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:hive/hive.dart';
import 'package:loftify/Utils/content_order_preference.dart';
import 'package:loftify/Utils/hive_util.dart';

void main() {
setUpAll(() async {
final directory = Directory('build/test_hive/content_order_preference');
await directory.create(recursive: true);
Hive.init(directory.absolute.path);
await Hive.openBox(HiveUtil.settingsBox);
});

setUp(() async {
if (!Hive.isBoxOpen(HiveUtil.settingsBox)) {
await Hive.openBox(HiveUtil.settingsBox);
}
await Hive.box(HiveUtil.settingsBox).clear();
});

tearDownAll(() async => Hive.close());

test('both content types default to descending order', () {
expect(ContentOrderPreference.read(ContentOrderType.collection), isFalse);
expect(ContentOrderPreference.read(ContentOrderType.grain), isFalse);
});

test('collection and grain remember independent choices', () async {
await ContentOrderPreference.write(ContentOrderType.collection, true);
expect(ContentOrderPreference.read(ContentOrderType.collection), isTrue);
expect(ContentOrderPreference.read(ContentOrderType.grain), isFalse);

await ContentOrderPreference.write(ContentOrderType.grain, true);
await ContentOrderPreference.write(ContentOrderType.collection, false);
expect(ContentOrderPreference.read(ContentOrderType.collection), isFalse);
expect(ContentOrderPreference.read(ContentOrderType.grain), isTrue);
});

test('saved order survives reopening the settings box', () async {
await ContentOrderPreference.write(ContentOrderType.collection, true);
await Hive.box(HiveUtil.settingsBox).close();
await Hive.openBox(HiveUtil.settingsBox);

expect(ContentOrderPreference.read(ContentOrderType.collection), isTrue);
});

test('invalid saved values fall back to descending order', () async {
await Hive.box(HiveUtil.settingsBox)
.put(HiveUtil.grainOldestFirstKey, 'invalid');
expect(ContentOrderPreference.read(ContentOrderType.grain), isFalse);
});
}
Loading