Skip to content
Open
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
6 changes: 5 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -898,5 +898,9 @@
"type": "String"
}
}
}
},
"invalidFile": "Invalid file",
"downloadFeed": "Download feed",
"asJSON": "As JSON",
"asOPML": "As OPML"
}
106 changes: 106 additions & 0 deletions lib/src/screens/settings/feed_settings_screen.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:auto_route/auto_route.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:interstellar/src/api/feed_source.dart';
import 'package:interstellar/src/controller/controller.dart';
import 'package:interstellar/src/controller/feed.dart';
Expand All @@ -14,6 +19,8 @@ import 'package:interstellar/src/screens/explore/explore_screen.dart';
import 'package:interstellar/src/screens/feed/feed_agregator.dart';
import 'package:interstellar/src/screens/feed/feed_screen.dart';
import 'package:interstellar/src/screens/settings/about_screen.dart';
import 'package:interstellar/src/utils/globals.dart';
import 'package:interstellar/src/utils/share.dart';
import 'package:interstellar/src/utils/utils.dart';
import 'package:interstellar/src/widgets/context_menu.dart';
import 'package:interstellar/src/widgets/list_tile_switch.dart';
Expand Down Expand Up @@ -119,6 +126,70 @@ class _FeedSettingsScreenState extends State<FeedSettingsScreen> {
),
icon: const Icon(Symbols.delete_rounded),
),
LoadingIconButton(
onPressed: () async => ContextMenu(
title: l(context).downloadFeed,
items: [
ContextMenuItem(
title: l(context).asJSON,
onTap: () async {
final feed = ac.feeds[entry.key]!;

final config = await ConfigShare.create(
type: ConfigShareType.feed,
name: entry.key,
payload: feed.toJson(),
);

final file = XFile.fromData(
Uint8List.fromList(
jsonEncode(config.toJson()).codeUnits,
),
mimeType: 'application/json',
);

if (!context.mounted) return;
await downloadFile(
context,
file,
'${entry.key}.json',
defaultDir: ac.defaultDownloadDir,
);
if (!context.mounted) return;
context.router.pop();
},
),
ContextMenuItem(
title: l(context).asOPML,
onTap: () async {
final feed = ac.feeds[entry.key]!;

final opml = convertFeedToOPML(
context,
entry.key,
feed,
);

final file = XFile.fromData(
Uint8List.fromList(opml.codeUnits),
mimeType: 'application/xml',
);

if (!context.mounted) return;
await downloadFile(
context,
file,
'${entry.key}.xml',
defaultDir: ac.defaultDownloadDir,
);
if (!context.mounted) return;
context.router.pop();
},
),
],
).openMenu(context),
icon: const Icon(Symbols.download_rounded),
),
IconButton(
onPressed: () async {
final feed = ac.feeds[entry.key]!;
Expand Down Expand Up @@ -165,6 +236,41 @@ class _FeedSettingsScreenState extends State<FeedSettingsScreen> {
leading: const Icon(Symbols.add_rounded),
title: Text(l(context).feeds_new),
onTap: () => newFeed(context),
trailing: LoadingTextButton(
onPressed: () async {
XFile? file;
try {
final result = await FilePicker.platform.pickFiles();
file = result?.files.single.xFile;
} catch (e) {
//
}
if (file == null) return;

final json = jsonDecode(await file.readAsString());

final config = ConfigShare.fromJson(json);

if (config.type != ConfigShareType.feed) {
if (!context.mounted) return;
scaffoldMessengerKey.currentState?.showSnackBar(
SnackBar(
content: Text(l(context).invalidFile),
showCloseIcon: true,
),
);
return;
}

final feed = Feed.fromJson({...config.payload});

if (!context.mounted) return;
await context.router.push(
EditFeedRoute(feed: config.name, feedData: feed),
);
},
label: Text(l(context).feeds_import),
),
),
],
),
Expand Down
178 changes: 125 additions & 53 deletions lib/src/screens/settings/filter_lists_screen.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:auto_route/auto_route.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:interstellar/src/controller/controller.dart';
import 'package:interstellar/src/controller/filter_list.dart';
import 'package:interstellar/src/controller/router.gr.dart';
import 'package:interstellar/src/models/config_share.dart';
import 'package:interstellar/src/screens/settings/about_screen.dart';
import 'package:interstellar/src/utils/globals.dart';
import 'package:interstellar/src/utils/share.dart';
import 'package:interstellar/src/utils/utils.dart';
import 'package:interstellar/src/widgets/list_tile_select.dart';
import 'package:interstellar/src/widgets/list_tile_switch.dart';
Expand Down Expand Up @@ -37,58 +44,82 @@ class _FilterListsScreenState extends State<FilterListsScreen> {
body: ListView(
children: [
...ac.filterLists.keys.map(
(name) => Row(
children: [
Expanded(
child: ListTile(
title: Text(name),
onTap: () => context.router.push(
EditFilterListRoute(filterList: name),
),
trailing: IconButton(
onPressed: () async {
final filterList = context
.read<AppController>()
.filterLists[name]!;

final config = await ConfigShare.create(
type: ConfigShareType.filterList,
name: name,
payload: filterList.toJson(),
);

if (!context.mounted) return;
var communityName = mbinConfigsCommunityName;
if (communityName.endsWith(
context.read<AppController>().instanceHost,
)) {
communityName = communityName.split('@').first;
}

final community = await context
.read<AppController>()
.api
.community
.getByName(communityName);

if (!context.mounted) return;

await context.router.push(
CreateRoute(
initTitle: '[Filter List] $name',
initBody:
'Short description here...\n\n${config.toMarkdown()}',
initCommunity: community,
),
);
},
icon: const Icon(Symbols.share_rounded),
),
(name) => ListTile(
title: Text(name),
onTap: () =>
context.router.push(EditFilterListRoute(filterList: name)),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
LoadingIconButton(
onPressed: () async {
final filterList = context
.read<AppController>()
.filterLists[name]!;

final config = await ConfigShare.create(
type: ConfigShareType.filterList,
name: name,
payload: filterList.toJson(),
);

final file = XFile.fromData(
Uint8List.fromList(
jsonEncode(config.toJson()).codeUnits,
),
mimeType: 'application/json',
);

if (!context.mounted) return;
await downloadFile(
context,
file,
'$name.json',
defaultDir: ac.defaultDownloadDir,
);
},
icon: const Icon(Symbols.download_rounded),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Switch(
IconButton(
onPressed: () async {
final filterList = context
.read<AppController>()
.filterLists[name]!;

final config = await ConfigShare.create(
type: ConfigShareType.filterList,
name: name,
payload: filterList.toJson(),
);

if (!context.mounted) return;
var communityName = mbinConfigsCommunityName;
if (communityName.endsWith(
context.read<AppController>().instanceHost,
)) {
communityName = communityName.split('@').first;
}

final community = await context
.read<AppController>()
.api
.community
.getByName(communityName);

if (!context.mounted) return;

await context.router.push(
CreateRoute(
initTitle: '[Filter List] $name',
initBody:
'Short description here...\n\n${config.toMarkdown()}',
initCommunity: community,
),
);
},
icon: const Icon(Symbols.share_rounded),
),
Switch(
value: ac.profile.filterLists[name] ?? false,
onChanged: (value) {
ac.updateProfile(
Expand All @@ -101,15 +132,56 @@ class _FilterListsScreenState extends State<FilterListsScreen> {
);
},
),
),
],
],
),
),
),
ListTile(
leading: const Icon(Symbols.add_rounded),
title: Text(l(context).filterList_new),
onTap: () =>
context.router.push(EditFilterListRoute(filterList: null)),
trailing: LoadingTextButton(
onPressed: () async {
XFile? file;
try {
final result = await FilePicker.platform.pickFiles();
file = result?.files.single.xFile;
} catch (e) {
//
}
if (file == null) return;

final json = jsonDecode(await file.readAsString());

final config = ConfigShare.fromJson(json);

if (config.type != ConfigShareType.filterList) {
if (!context.mounted) return;
scaffoldMessengerKey.currentState?.showSnackBar(
SnackBar(
content: Text(l(context).invalidFile),
showCloseIcon: true,
),
);
return;
}

final filterList = FilterList.fromJson({
...config.payload,
'name': config.name,
});

if (!context.mounted) return;
await context.router.push(
EditFilterListRoute(
filterList: config.name,
importFilterList: filterList,
),
);
},
label: Text(l(context).filterList_import),
),
),
],
),
Expand Down
Loading
Loading