~cytrogen/fluent-reader-mobile

8864d2f827c4a6a9922ef8696be31c95ee7b1f24 — Bruce Liu 5 years ago 04b0485
add uncategorized group
M assets/article/article.css => assets/article/article.css +4 -0
@@ 126,4 126,8 @@ article ul, article menu, article dir {
}
article li {
    overflow: visible;
}
article pre {
    white-space: pre-wrap;
    word-break: break-all;
}
\ No newline at end of file

M lib/l10n/intl_en.arb => lib/l10n/intl_en.arb +3 -1
@@ 88,5 88,7 @@
    "retry": "Retry",
    "copy": "Copy",
    "errorLog": "Error log",
    "unreadSourceTip": "You can long press on the title of this page to toggle between all and unread subscriptions."
    "unreadSourceTip": "You can long press on the title of this page to toggle between all and unread subscriptions.",
    "uncategorized": "Uncategorized",
    "showUncategorized": "Show uncategorized"
  }
\ No newline at end of file

M lib/l10n/intl_zh.arb => lib/l10n/intl_zh.arb +3 -1
@@ 88,5 88,7 @@
    "retry": "重试",
    "copy": "复制",
    "errorLog": "错误日志",
    "unreadSourceTip": "您可以长按此页面的标题来切换全部订阅源或仅未读订阅源。"
    "unreadSourceTip": "您可以长按此页面的标题来切换全部订阅源或仅未读订阅源。",
    "uncategorized": "未分组",
    "showUncategorized": "显示“未分组”"
  }
\ No newline at end of file

M lib/models/groups_model.dart => lib/models/groups_model.dart +30 -0
@@ 1,8 1,10 @@
import 'package:fluent_reader_lite/utils/global.dart';
import 'package:fluent_reader_lite/utils/store.dart';
import 'package:flutter/cupertino.dart';

class GroupsModel with ChangeNotifier {
  Map<String, List<String>> _groups = Store.getGroups();
  List<String> uncategorized = Store.getUncategorized();

  Map<String, List<String>> get groups => _groups;
  set groups(Map<String, List<String>> groups) {


@@ 10,4 12,32 @@ class GroupsModel with ChangeNotifier {
    notifyListeners();
    Store.setGroups(groups);
  }

  void updateUncategorized({force: false}) {
    if (uncategorized != null || force) {
      final sids = Set<String>.from(
        Global.sourcesModel.getSources().map<String>((s) => s.id)
      );
      for (var group in _groups.values) {
        for (var sid in group) {
          sids.remove(sid);
        }
      }
      uncategorized = sids.toList();
      Store.setUncategorized(uncategorized);
    }
  }

  bool get showUncategorized => uncategorized != null;
  set showUncategorized(bool value) {
    if (showUncategorized != value) {
      if (value) {
        updateUncategorized(force: true);
      } else {
        uncategorized = null;
        Store.setUncategorized(null);
      }
      notifyListeners();
    }
  }
}
\ No newline at end of file

M lib/pages/group_list_page.dart => lib/pages/group_list_page.dart +23 -5
@@ 18,6 18,8 @@ class GroupListPage extends StatefulWidget {
}

class _GroupListPageState extends State<GroupListPage> {
  static const List<String> _uncategorizedIndicator = [null, null];

  int _unreadCount(Iterable<RSSSource> sources) {
    return sources.fold(0, (c, s) => c + (s != null ? s.unreadCount : 0));
  }


@@ 55,27 57,43 @@ class _GroupListPageState extends State<GroupListPage> {
      builder: (context, groupsModel, sourcesModel, child) {
        final groupNames = groupsModel.groups.keys.toList();
        groupNames.sort(Utils.localStringCompare);
        if (groupsModel.uncategorized != null) {
          groupNames.insert(0, null);
        }
        return SliverList(
          delegate: SliverChildBuilderDelegate((context, index) {
            final groupName = groupNames[index];
            String groupName;
            List<String> group;
            final isUncategorized = groupsModel.showUncategorized && index == 0;
            if (isUncategorized) {
              groupName = S.of(context).uncategorized;
              group = groupsModel.uncategorized;
            } else {
              groupName = groupNames[index];
              group = groupsModel.groups[groupName];
            }
            final count = _unreadCount(
              groupsModel.groups[groupName].map((sid) => sourcesModel.getSource(sid))
              group.map((sid) => sourcesModel.getSource(sid))
            );
            final tile = MyListTile(
              title: Flexible(child: Text(groupName, overflow: TextOverflow.ellipsis)),
              trailing: count > 0 ? Badge(count) : null,
              onTap: () { Navigator.of(context).pop([groupName]); },
              onTap: () { 
                Navigator.of(context).pop(
                  isUncategorized ? _uncategorizedIndicator : [groupName]
                );
              },
              background: CupertinoColors.systemBackground,
            );
            return Dismissible(
              key: Key(groupName),
              key: Key("$groupName$index"),
              child: tile,
              background: dismissBg,
              direction: DismissDirection.startToEnd,
              dismissThresholds: _dismissThresholds,
              confirmDismiss: (_) async {
                HapticFeedback.mediumImpact();
                Set<String> sids = Set.from(groupsModel.groups[groupName]);
                Set<String> sids = Set.from(group);
                showCupertinoModalPopup(
                  context: context,
                  builder: (context) => MarkAllActionSheet(sids),

M lib/pages/settings/feed_page.dart => lib/pages/settings/feed_page.dart +43 -25
@@ 2,6 2,7 @@ import 'package:fluent_reader_lite/components/list_tile_group.dart';
import 'package:fluent_reader_lite/components/my_list_tile.dart';
import 'package:fluent_reader_lite/generated/l10n.dart';
import 'package:fluent_reader_lite/models/feeds_model.dart';
import 'package:fluent_reader_lite/models/groups_model.dart';
import 'package:fluent_reader_lite/utils/colors.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';


@@ 56,35 57,52 @@ class FeedPage extends StatelessWidget {
            ItemSwipeOption.OpenExternal: S.of(context).openExternal,
            ItemSwipeOption.OpenMenu: S.of(context).openMenu,
          };
          return ListView(
            children: [
              ListTileGroup([
                MyListTile(
                  title: Text(S.of(context).showThumb),
                  trailing: CupertinoSwitch(
                    value: feedsModel.showThumb,
                    onChanged: (v) { feedsModel.showThumb = v; },
                  ),
                  trailingChevron: false,
                ),
                MyListTile(
                  title: Text(S.of(context).showSnippet),
                  trailing: CupertinoSwitch(
                    value: feedsModel.showSnippet,
                    onChanged: (v) { feedsModel.showSnippet = v; },
                  ),
                  trailingChevron: false,
                ),
                MyListTile(
                  title: Text(S.of(context).dimRead),
          final preferences = ListTileGroup([
            MyListTile(
              title: Text(S.of(context).showThumb),
              trailing: CupertinoSwitch(
                value: feedsModel.showThumb,
                onChanged: (v) { feedsModel.showThumb = v; },
              ),
              trailingChevron: false,
            ),
            MyListTile(
              title: Text(S.of(context).showSnippet),
              trailing: CupertinoSwitch(
                value: feedsModel.showSnippet,
                onChanged: (v) { feedsModel.showSnippet = v; },
              ),
              trailingChevron: false,
            ),
            MyListTile(
              title: Text(S.of(context).dimRead),
              trailing: CupertinoSwitch(
                value: feedsModel.dimRead,
                onChanged: (v) { feedsModel.dimRead = v; },
              ),
              trailingChevron: false,
              withDivider: false,
            ),
          ], title: S.of(context).preferences);
          final groups = ListTileGroup([
            Consumer<GroupsModel>(
              builder: (context, groupsModel, child) {
                return MyListTile(
                  title: Text(S.of(context).showUncategorized),
                  trailing: CupertinoSwitch(
                    value: feedsModel.dimRead,
                    onChanged: (v) { feedsModel.dimRead = v; },
                    value: groupsModel.showUncategorized,
                    onChanged: (v) { groupsModel.showUncategorized = v; },
                  ),
                  trailingChevron: false,
                  withDivider: false,
                ),
              ], title: S.of(context).preferences),
                );
              },
            ),
          ], title: S.of(context).groups);
          return ListView(
            children: [
              preferences,
              groups,
              ListTileGroup([
                MyListTile(
                  title: Text(S.of(context).swipeRight),

M lib/pages/subscription_list_page.dart => lib/pages/subscription_list_page.dart +6 -0
@@ 73,11 73,17 @@ class _SubscriptionListPageState extends State<SubscriptionListPage> {
    }
    if (!mounted) return;
    if (result != null) {
      _onScrollTop();
      if (result.length == 0) {
        setState(() {
          title = null;
          sids = null;
        });
      } else if (result.length > 1) {
        setState(() {
          title = S.of(context).uncategorized;
          sids = Global.groupsModel.uncategorized;
        });
      } else {
        setState(() {
          title = result[0];

M lib/utils/store.dart => lib/utils/store.dart +16 -0
@@ 7,6 7,7 @@ import 'package:shared_preferences/shared_preferences.dart';
abstract class StoreKeys {
  static const GROUPS = "groups";
  static const ERROR_LOG = "errorLog";
  static const UNCATEGORIZED = "uncategorized";
 
  // General
  static const THEME = "theme";


@@ 92,6 93,21 @@ class Store {
    sp.setString(StoreKeys.GROUPS, jsonEncode(groups));
  }

  static List<String> getUncategorized() {
    final stored = sp.getString(StoreKeys.UNCATEGORIZED);
    if (stored == null) return null;
    final parsed = jsonDecode(stored);
    return List.castFrom(parsed);
  }

  static void setUncategorized(List<String> value) {
    if (value == null) {
      sp.remove(StoreKeys.UNCATEGORIZED);
    } else {
      sp.setString(StoreKeys.UNCATEGORIZED, jsonEncode(value));
    }
  }

  static int getArticleFontSize() {
    return sp.getInt(StoreKeys.ARTICLE_FONT_SIZE) ?? 16;
  }