multi account boosting!!!!!!!!!!!!!!!!!!!!!!!!!!!!

This commit is contained in:
zoe 2022-08-22 20:08:10 +02:00
parent d973c921bd
commit af002b875f
8 changed files with 298 additions and 29 deletions

View File

@ -0,0 +1,119 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:loris/global.dart' as global;
import 'package:http/http.dart' as http;
enum InteractionType {
favorite,
reblog,
}
extension InteractionTypeExtension on InteractionType {
IconData get icon {
switch (this) {
case InteractionType.favorite:
return Icons.favorite_border;
case InteractionType.reblog:
return Icons.repeat_sharp;
}
}
IconData get revokeIcon {
switch (this) {
case InteractionType.favorite:
return Icons.favorite;
case InteractionType.reblog:
return Icons.repeat_on_sharp;
}
}
String get slug {
switch (this) {
case InteractionType.favorite:
return "favourite";
case InteractionType.reblog:
return "reblog";
}
}
String get revokeSlug {
switch (this) {
case InteractionType.favorite:
return "unfavourite";
case InteractionType.reblog:
return "unreblog";
}
}
}
Future<int> makeInteractionFromId(
String id,
String postid,
InteractionType type, {
bool revoke = false,
}) async {
Map<String, String> headers =
global.settings!.identities[id]!.getAuthHeaders();
headers.addAll(global.defaultHeaders);
final uri = Uri(
scheme: "https",
path: "/api/v1/statuses/$postid/${revoke ? type.revokeSlug : type.slug}",
host: global.settings!.identities[id]!.instanceUrl,
);
final response = await http.post(uri, headers: headers);
return response.statusCode;
}
Future<int> makeInteractionFromUrl(
String id,
String posturl,
InteractionType type, {
bool revoke = false,
}) async {
Map<String, String> headers =
global.settings!.identities[id]!.getAuthHeaders();
headers.addAll(global.defaultHeaders);
final uri = Uri(
scheme: "https",
host: global.settings!.identities[id]!.instanceUrl,
path: "api/v1/search",
queryParameters: {
"type": "statuses",
"q": posturl,
},
);
final response = await http.get(uri, headers: headers);
if (response.statusCode != 200) {
return response.statusCode;
}
final Map<String, dynamic> json = jsonDecode(response.body);
final List<dynamic> statuses = json["statuses"];
if (statuses.isEmpty) {
return 404;
}
print(response.body);
final String postid = statuses[0]["id"];
return await makeInteractionFromId(id, postid, type);
}
Future<int> makeFullInteraction(
String id,
String postid,
String posturl,
InteractionType type, {
bool revoke = false,
}) async {
int i = await makeInteractionFromId(id, postid, type);
if (i == 200) {
return i;
}
return await makeInteractionFromUrl(id, posturl, type);
}

View File

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:loris/business_logic/interactions/interactions.dart';
class InteractionMenu extends StatefulWidget {
const InteractionMenu({required this.type, Key? key}) : super(key: key);
final InteractionType type;
@override
State<InteractionMenu> createState() => _InteractionMenuState();
}
class _InteractionMenuState extends State<InteractionMenu> {
@override
Widget build(BuildContext context) {
return SimpleDialog();
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../business_logic/posting/posting.dart' as logic;
class MakePost extends StatelessWidget {
const MakePost({Key? key}) : super(key: key);

View File

@ -0,0 +1,121 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:loris/business_logic/timeline/timeline.dart';
import '../business_logic/interactions/interactions.dart' as interactions;
import 'package:loris/global.dart' as global;
class InteractionButton extends StatefulWidget {
const InteractionButton({required this.model, required this.type, Key? key})
: super(key: key);
final interactions.InteractionType type;
final PostModel model;
@override
State<InteractionButton> createState() => _InteractionButtonState();
}
class _InteractionButtonState extends State<InteractionButton> {
IconData icon = Icons.question_mark;
String idkey = global.settings!.activeIdentity;
bool active = false;
bool busy = false;
@override
void initState() {
switch (widget.type) {
case interactions.InteractionType.favorite:
active = widget.model.favourited;
break;
case interactions.InteractionType.reblog:
active = widget.model.reblogged;
break;
}
icon = active ? widget.type.revokeIcon : widget.type.icon;
super.initState();
}
Future<void> sendRequest({
required String id,
bool fromUrl = false,
}) async {
if (!busy) {
busy = true;
int status = fromUrl
? await interactions.makeFullInteraction(
id,
widget.model.id,
widget.model.uri,
widget.type,
revoke: active,
)
: await interactions.makeInteractionFromId(
id,
widget.model.id,
widget.type,
revoke: active,
);
if (status == 200 && mounted) {
setState(() {
active = !active;
icon = active ? widget.type.revokeIcon : widget.type.icon;
});
} else {
await showError(status);
}
busy = false;
}
}
Future<void> showError(int status) async {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("error: $status")));
setState(() {
icon = Icons.dangerous;
});
await Future.delayed(const Duration(seconds: 1));
setState(() {
icon = active ? widget.type.revokeIcon : widget.type.icon;
});
}
@override
Widget build(BuildContext context) {
if (global.settings!.identities.length == 1) {
return IconButton(
onPressed: () async {
await sendRequest(id: global.settings!.identities.keys.first);
},
icon: Icon(icon),
);
}
// user is logged into multiple accounts
List<PopupMenuItem> identityPickers = [];
global.settings!.identities.forEach(
(key, value) {
identityPickers.add(
PopupMenuItem(
value: key,
child: Text(key),
),
);
},
);
return PopupMenuButton(
onSelected: ((value) {
setState(() {
idkey = value as String;
});
sendRequest(
id: value as String,
fromUrl: true,
);
}),
itemBuilder: (context) => identityPickers,
initialValue: idkey,
icon: Icon(icon),
);
}
}

View File

@ -22,9 +22,11 @@ class _MainScaffoldState extends State<MainScaffold> {
void initState() {
notifStream.stream.listen((event) {
if (index != 2) {
setState(() {
unreadNotifs = event;
});
if (mounted) {
setState(() {
unreadNotifs = event;
});
}
}
});
super.initState();

View File

@ -2,10 +2,12 @@ import 'package:flutter/material.dart';
import 'package:localization/localization.dart';
import 'package:loris/business_logic/account/account.dart';
import 'package:loris/business_logic/timeline/media.dart';
import 'package:loris/partials/interaction_button.dart';
import 'package:loris/partials/media_attachment.dart';
import 'package:loris/partials/post_options.dart';
import 'package:loris/partials/post_text_renderer.dart';
import '../business_logic/timeline/timeline.dart' as tl;
import '../business_logic/interactions/interactions.dart' as interactions;
class Post extends StatefulWidget {
const Post({required this.model, Key? key}) : super(key: key);
@ -29,7 +31,7 @@ class _PostState extends State<Post> {
spoilerText: widget.model.spoilerText,
media: widget.model.attachments,
),
postActionBar(context, widget.model),
PostActionBar(model: widget.model),
RebloggedBy(account: widget.model.rebloggedBy),
],
);
@ -226,28 +228,36 @@ class _PostBodyState extends State<PostBody> {
}
}
Widget postActionBar(context, tl.PostModel model) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.reply),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.repeat),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.favorite_outline),
),
IconButton(
onPressed: () {
popupPostOptions(context, model);
},
icon: const Icon(Icons.more_horiz),
)
],
);
class PostActionBar extends StatefulWidget {
const PostActionBar({required this.model, Key? key}) : super(key: key);
final tl.PostModel model;
@override
State<PostActionBar> createState() => _PostActionBarState();
}
class _PostActionBarState extends State<PostActionBar> {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(onPressed: () {}, icon: const Icon(Icons.reply)),
InteractionButton(
model: widget.model,
type: interactions.InteractionType.reblog,
),
InteractionButton(
model: widget.model,
type: interactions.InteractionType.favorite,
),
IconButton(
onPressed: () {
popupPostOptions(context, widget.model);
},
icon: const Icon(Icons.more_horiz),
)
],
);
}
}

View File

@ -26,7 +26,6 @@ class Thread extends StatelessWidget {
56,
constraints: BoxConstraints(
maxWidth: global.settings!.maxPostWidth,
minWidth: 375,
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,