fix buttons
This commit is contained in:
parent
2c2e66d842
commit
1b164411ea
|
@ -1,9 +1,13 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localization/localization.dart';
|
||||
import 'package:loris/business_logic/network_tools/get_post_from_url.dart';
|
||||
import 'package:loris/global.dart' as global;
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../timeline/timeline.dart';
|
||||
|
||||
enum InteractionType {
|
||||
favorite,
|
||||
reblog,
|
||||
|
@ -45,6 +49,15 @@ extension InteractionTypeExtension on InteractionType {
|
|||
return "unreblog";
|
||||
}
|
||||
}
|
||||
|
||||
String get name {
|
||||
switch (this) {
|
||||
case InteractionType.favorite:
|
||||
return "like".i18n();
|
||||
case InteractionType.reblog:
|
||||
return "reblog".i18n();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> makeInteractionFromId(
|
||||
|
@ -73,48 +86,10 @@ Future<int> makeInteractionFromUrl(
|
|||
InteractionType type, {
|
||||
bool revoke = false,
|
||||
}) async {
|
||||
Map<String, String> headers =
|
||||
global.settings!.identities[id]!.getAuthHeaders();
|
||||
headers.addAll(global.defaultHeaders);
|
||||
|
||||
final uriv1 = Uri(
|
||||
scheme: "https",
|
||||
host: global.settings!.identities[id]!.instanceUrl,
|
||||
path: "api/v1/search",
|
||||
queryParameters: {
|
||||
"resolve": "true",
|
||||
"type": "statuses",
|
||||
"q": posturl,
|
||||
},
|
||||
);
|
||||
|
||||
http.Response response = await http.get(uriv1, headers: headers);
|
||||
if (response.statusCode != 200) {
|
||||
final uriv2 = Uri(
|
||||
scheme: "https",
|
||||
host: global.settings!.identities[id]!.instanceUrl,
|
||||
path: "api/v2/search",
|
||||
queryParameters: {
|
||||
"type": "statuses",
|
||||
"q": posturl,
|
||||
"resolve": "true",
|
||||
},
|
||||
);
|
||||
response = await http.get(uriv2, 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;
|
||||
}
|
||||
final String postid = statuses[0]["id"];
|
||||
|
||||
return await makeInteractionFromId(id, postid, type);
|
||||
Map<int, PostModel?> post = await getPostFromUrl(id, posturl);
|
||||
return post.keys.first == 200
|
||||
? await makeInteractionFromId(id, post[post.keys.first]!.id, type)
|
||||
: post.keys.first;
|
||||
}
|
||||
|
||||
Future<int> makeFullInteraction(
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:loris/business_logic/timeline/timeline.dart';
|
||||
import 'package:loris/global.dart' as global;
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
Future<Map<int, PostModel?>> getPostFromUrl(
|
||||
String id,
|
||||
String posturl,
|
||||
) async {
|
||||
Map<String, String> headers =
|
||||
global.settings!.identities[id]!.getAuthHeaders();
|
||||
headers.addAll(global.defaultHeaders);
|
||||
|
||||
final uriv1 = Uri(
|
||||
scheme: "https",
|
||||
host: global.settings!.identities[id]!.instanceUrl,
|
||||
path: "api/v1/search",
|
||||
queryParameters: {
|
||||
"resolve": "true",
|
||||
"type": "statuses",
|
||||
"q": posturl,
|
||||
},
|
||||
);
|
||||
|
||||
http.Response response = await http.get(uriv1, headers: headers);
|
||||
if (response.statusCode != 200) {
|
||||
final uriv2 = Uri(
|
||||
scheme: "https",
|
||||
host: global.settings!.identities[id]!.instanceUrl,
|
||||
path: "api/v2/search",
|
||||
queryParameters: {
|
||||
"type": "statuses",
|
||||
"q": posturl,
|
||||
"resolve": "true",
|
||||
},
|
||||
);
|
||||
response = await http.get(uriv2, headers: headers);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return {response.statusCode: null};
|
||||
}
|
||||
|
||||
final Map<String, dynamic> json = jsonDecode(response.body);
|
||||
final List<dynamic> statuses = json["statuses"];
|
||||
if (statuses.isEmpty) {
|
||||
return {404: null};
|
||||
}
|
||||
return {200: PostModel.fromJson(statuses[0], id)};
|
||||
}
|
|
@ -22,7 +22,7 @@ class NotificationModel implements Comparable {
|
|||
type = NotificationType.values.firstWhere(
|
||||
(element) => element.name == "NotificationType.${json["type"]}");
|
||||
if (json["status"] != null) {
|
||||
post = PostModel.fromJson(json["status"]);
|
||||
post = PostModel.fromJson(json["status"], id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,23 @@ class TimelinePartModel {
|
|||
|
||||
enum Visibility { public, unlisted, private, direct }
|
||||
|
||||
extension VisibilityExtension on Visibility {
|
||||
bool get boostable {
|
||||
switch (this) {
|
||||
case Visibility.direct:
|
||||
return false;
|
||||
case Visibility.private:
|
||||
return false;
|
||||
case Visibility.public:
|
||||
return true;
|
||||
case Visibility.unlisted:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PostModel implements Comparable {
|
||||
late String identity;
|
||||
late String id;
|
||||
late String? reblogId;
|
||||
late String createdAt;
|
||||
|
@ -29,7 +45,7 @@ class PostModel implements Comparable {
|
|||
// exists if post is a reblog
|
||||
late String originalId;
|
||||
|
||||
PostModel.fromJson(Map<String, dynamic> json) {
|
||||
PostModel.fromJson(Map<String, dynamic> json, this.identity) {
|
||||
id = json["id"] as String;
|
||||
reblogId = null;
|
||||
createdAt = json["created_at"];
|
||||
|
@ -41,8 +57,6 @@ class PostModel implements Comparable {
|
|||
} else {
|
||||
rebloggedBy = null;
|
||||
}
|
||||
|
||||
// regular
|
||||
originalId = json["id"];
|
||||
uri = json["uri"] as String;
|
||||
content = json["content"] as String;
|
||||
|
@ -91,7 +105,7 @@ class PostModel implements Comparable {
|
|||
List<PostModel> posts = [this];
|
||||
int i = 0;
|
||||
while (i < ancestorsJson.length) {
|
||||
posts.add(PostModel.fromJson(ancestorsJson[i]));
|
||||
posts.add(PostModel.fromJson(ancestorsJson[i], activeId));
|
||||
i++;
|
||||
}
|
||||
return ThreadModel(posts);
|
||||
|
@ -159,7 +173,7 @@ Future<List<ThreadModel>> getTimelineFromServer(
|
|||
final List<dynamic> json = await jsonDecode(response.body);
|
||||
List<ThreadModel> threads = [];
|
||||
for (int i = 0; i < json.length; i++) {
|
||||
threads.add(await PostModel.fromJson(json[i]).getThread());
|
||||
threads.add(await PostModel.fromJson(json[i], identity).getThread());
|
||||
}
|
||||
return threads;
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -1,14 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:loris/business_logic/network_tools/get_post_from_url.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})
|
||||
const InteractionButton(
|
||||
{required this.model,
|
||||
required this.type,
|
||||
Key? key,
|
||||
this.extended = false})
|
||||
: super(key: key);
|
||||
final interactions.InteractionType type;
|
||||
final PostModel model;
|
||||
final bool extended;
|
||||
|
||||
@override
|
||||
State<InteractionButton> createState() => _InteractionButtonState();
|
||||
|
@ -19,17 +25,23 @@ class _InteractionButtonState extends State<InteractionButton> {
|
|||
String idkey = global.settings!.activeIdentity;
|
||||
bool active = false;
|
||||
bool busy = false;
|
||||
bool success = false;
|
||||
// user is logged into multiple accounts
|
||||
|
||||
// decides if the button has been clicked already
|
||||
bool isActive(PostModel post) {
|
||||
switch (widget.type) {
|
||||
case interactions.InteractionType.favorite:
|
||||
return widget.model.favourited;
|
||||
case interactions.InteractionType.reblog:
|
||||
return widget.model.reblogged;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
switch (widget.type) {
|
||||
case interactions.InteractionType.favorite:
|
||||
active = widget.model.favourited;
|
||||
break;
|
||||
case interactions.InteractionType.reblog:
|
||||
active = widget.model.reblogged;
|
||||
break;
|
||||
}
|
||||
active = isActive(widget.model);
|
||||
|
||||
icon = active ? widget.type.revokeIcon : widget.type.icon;
|
||||
super.initState();
|
||||
}
|
||||
|
@ -39,7 +51,9 @@ class _InteractionButtonState extends State<InteractionButton> {
|
|||
bool fromUrl = false,
|
||||
}) async {
|
||||
if (!busy) {
|
||||
busy = true;
|
||||
setState(() {
|
||||
busy = true;
|
||||
});
|
||||
int status = fromUrl
|
||||
? await interactions.makeFullInteraction(
|
||||
id,
|
||||
|
@ -55,15 +69,19 @@ class _InteractionButtonState extends State<InteractionButton> {
|
|||
revoke: active,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
busy = false;
|
||||
});
|
||||
|
||||
if (status == 200 && mounted) {
|
||||
setState(() {
|
||||
active = !active;
|
||||
icon = active ? widget.type.revokeIcon : widget.type.icon;
|
||||
});
|
||||
showSuccess();
|
||||
} else {
|
||||
await showError(status);
|
||||
}
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,45 +97,126 @@ class _InteractionButtonState extends State<InteractionButton> {
|
|||
});
|
||||
}
|
||||
|
||||
Future<Map<String, PostModel?>> updateIdentities() async {
|
||||
Map<String, PostModel?> idList = {};
|
||||
for (int i = 0; i < global.settings!.identities.length; i++) {
|
||||
final Map<int, PostModel?> post = await getPostFromUrl(
|
||||
global.settings!.identities.keys.toList()[i], widget.model.uri);
|
||||
if (post.keys.first == 200) {
|
||||
idList.addAll({
|
||||
global.settings!.identities.keys.toList()[i]: post[post.keys.first]
|
||||
});
|
||||
}
|
||||
}
|
||||
return idList;
|
||||
}
|
||||
|
||||
Future<void> showSuccess() async {
|
||||
setState(() {
|
||||
success = true;
|
||||
});
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
setState(() {
|
||||
success = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (global.settings!.identities.length == 1) {
|
||||
if (!widget.model.visibility.boostable &&
|
||||
widget.type == interactions.InteractionType.reblog) {
|
||||
return const Icon(Icons.lock);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return const Icon(Icons.check);
|
||||
}
|
||||
|
||||
if (busy) {
|
||||
return Center(
|
||||
heightFactor: 1,
|
||||
widthFactor: 1,
|
||||
child: SizedBox(
|
||||
height: Theme.of(context).iconTheme.size,
|
||||
width: Theme.of(context).iconTheme.size,
|
||||
child: const CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (global.settings!.identities.length == 1 || !widget.extended) {
|
||||
return IconButton(
|
||||
tooltip: widget.type.name,
|
||||
onPressed: () async {
|
||||
await sendRequest(id: global.settings!.identities.keys.first);
|
||||
await sendRequest(
|
||||
id: widget.model.identity,
|
||||
);
|
||||
},
|
||||
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) {
|
||||
if (mounted) {
|
||||
return IconButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
idkey = value as String;
|
||||
busy = true;
|
||||
});
|
||||
}
|
||||
sendRequest(
|
||||
id: value as String,
|
||||
fromUrl: true,
|
||||
);
|
||||
}),
|
||||
itemBuilder: (context) => identityPickers,
|
||||
initialValue: idkey,
|
||||
icon: Icon(icon),
|
||||
);
|
||||
final idList = await updateIdentities();
|
||||
setState(() {
|
||||
busy = false;
|
||||
});
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: ((context) {
|
||||
List<Widget> c = [
|
||||
SelectableText(
|
||||
widget.type.name,
|
||||
style: Theme.of(context).textTheme.displayLarge,
|
||||
)
|
||||
];
|
||||
idList.forEach(
|
||||
(key, value) {
|
||||
if (value != null) {
|
||||
c.add(
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
setState(() {
|
||||
busy = true;
|
||||
});
|
||||
final result =
|
||||
await interactions.makeInteractionFromId(
|
||||
key,
|
||||
value.id,
|
||||
widget.type,
|
||||
revoke: isActive(value),
|
||||
);
|
||||
setState(() {
|
||||
busy = false;
|
||||
});
|
||||
if (result != 200) {
|
||||
showError(result);
|
||||
} else {
|
||||
showSuccess();
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
isActive(value)
|
||||
? widget.type.revokeIcon
|
||||
: widget.type.icon,
|
||||
),
|
||||
label: Text(key),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: c,
|
||||
);
|
||||
}));
|
||||
},
|
||||
icon: Icon(icon));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,22 +250,33 @@ class _PostActionBarState extends State<PostActionBar> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
IconButton(onPressed: () {}, icon: const Icon(Icons.reply)),
|
||||
InteractionButton(
|
||||
model: widget.model,
|
||||
type: interactions.InteractionType.reblog,
|
||||
Expanded(
|
||||
flex: 20,
|
||||
child: IconButton(onPressed: () {}, icon: const Icon(Icons.reply))),
|
||||
Expanded(
|
||||
flex: 20,
|
||||
child: InteractionButton(
|
||||
model: widget.model,
|
||||
type: interactions.InteractionType.reblog,
|
||||
),
|
||||
),
|
||||
InteractionButton(
|
||||
model: widget.model,
|
||||
type: interactions.InteractionType.favorite,
|
||||
Expanded(
|
||||
flex: 20,
|
||||
child: InteractionButton(
|
||||
model: widget.model,
|
||||
type: interactions.InteractionType.favorite,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
popupPostOptions(context, widget.model);
|
||||
},
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
Expanded(
|
||||
flex: 20,
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
popupPostOptions(context, widget.model);
|
||||
},
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
|
|
@ -49,6 +49,7 @@ ThemeData getTheme(CustomColors colors) {
|
|||
),
|
||||
),
|
||||
iconTheme: IconThemeData(
|
||||
size: 24,
|
||||
color: colors.colorScheme.onSurface,
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
|
@ -130,6 +131,7 @@ ThemeData getTheme(CustomColors colors) {
|
|||
selectedRowColor: colors.colorScheme.background,
|
||||
textSelectionTheme:
|
||||
TextSelectionThemeData(selectionColor: colors.hintColor),
|
||||
primaryIconTheme: const IconThemeData(size: 24),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue