start dm feature
This commit is contained in:
parent
aa46264c64
commit
a73614b5cc
|
@ -0,0 +1,86 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:loris/business_logic/account/account.dart';
|
||||
import 'package:loris/business_logic/timeline/timeline.dart';
|
||||
import 'package:loris/global.dart' as global;
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ConversationModel {
|
||||
final String id;
|
||||
final List<AccountModel> accounts;
|
||||
final bool unread;
|
||||
final PostModel? lastStatus;
|
||||
final String identity;
|
||||
|
||||
ConversationModel(
|
||||
{required this.id,
|
||||
required this.accounts,
|
||||
required this.unread,
|
||||
this.lastStatus,
|
||||
required this.identity});
|
||||
|
||||
factory ConversationModel.fromJson(
|
||||
Map<String, dynamic> json, String identity) {
|
||||
final List accountsJson = json["accounts"];
|
||||
final List<AccountModel> accounts = accountsJson
|
||||
.map(
|
||||
(e) => AccountModel.fromJson(e, identity),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return ConversationModel(
|
||||
accounts: accounts,
|
||||
id: json["id"],
|
||||
identity: identity,
|
||||
unread: json["unread"],
|
||||
lastStatus: PostModel.fromJson(json["last_status"], identity));
|
||||
}
|
||||
}
|
||||
|
||||
class ConversationModelResult {
|
||||
// http status code
|
||||
final int statusCode;
|
||||
|
||||
// list of models
|
||||
final List<ConversationModel> models;
|
||||
|
||||
ConversationModelResult(this.statusCode, this.models);
|
||||
}
|
||||
|
||||
/*
|
||||
loads conversation models from timeline
|
||||
*/
|
||||
Future<ConversationModelResult> getConversationModels(
|
||||
String identityName,
|
||||
String? maxId,
|
||||
) async {
|
||||
final settings = global.settings!;
|
||||
final identity = global.settings!.identities[identityName]!;
|
||||
final headers = {
|
||||
...identity.getAuthHeaders(),
|
||||
...global.defaultHeaders,
|
||||
};
|
||||
final Map<String, String> queries = {
|
||||
"limit": settings.batchSize.toString(),
|
||||
if (maxId != null) "max_id": maxId,
|
||||
};
|
||||
final uri = Uri(
|
||||
scheme: "https",
|
||||
host: identity.instanceUrl,
|
||||
queryParameters: queries,
|
||||
path: "/api/v1/conversations",
|
||||
);
|
||||
|
||||
final result = await http.get(uri, headers: headers);
|
||||
if (result.statusCode != 200) {
|
||||
return ConversationModelResult(result.statusCode, []);
|
||||
}
|
||||
|
||||
return ConversationModelResult(
|
||||
result.statusCode,
|
||||
jsonDecode(result.body)
|
||||
.map<ConversationModel>(
|
||||
(e) => ConversationModel.fromJson(e, identityName),
|
||||
)
|
||||
.toList());
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:localization/localization.dart';
|
||||
import 'package:loris/business_logic/fileupload/fileupload.dart';
|
||||
import 'package:loris/business_logic/instance/instance.dart';
|
||||
|
@ -315,8 +316,9 @@ class _MakePostState extends State<MakePost> {
|
|||
spoilerText = value;
|
||||
})),
|
||||
),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
Container(
|
||||
constraints:
|
||||
BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.6),
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
initialValue: replyAts,
|
||||
|
|
|
@ -1,5 +1,90 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loris/business_logic/chat/chat.dart';
|
||||
import 'package:loris/themes/themes.dart' as themes;
|
||||
import 'package:loris/global.dart' as global;
|
||||
|
||||
Widget chat(context) {
|
||||
return const Center(child: Text("Chat"));
|
||||
class Chat extends StatefulWidget {
|
||||
const Chat({super.key});
|
||||
|
||||
@override
|
||||
State<Chat> createState() => _ChatState();
|
||||
}
|
||||
|
||||
class _ChatState extends State<Chat> {
|
||||
List<ConversationModel> conversations = [];
|
||||
// map that stores max ids for each identity
|
||||
Map<String, String?> maxIds = {};
|
||||
|
||||
Future<void> fetchForIdentity(String identityName) async {
|
||||
final c = await getConversationModels(identityName, maxIds[identityName]);
|
||||
setState(() {
|
||||
conversations.addAll(c.models);
|
||||
});
|
||||
}
|
||||
|
||||
void updateConversations() {
|
||||
global.settings!.identities.forEach((key, value) {
|
||||
fetchForIdentity(key);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
updateConversations();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) => ConversationButton(
|
||||
model: conversations[index],
|
||||
),
|
||||
separatorBuilder: (context, index) => const Divider(
|
||||
height: themes.defaultSeperatorHeight,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
itemCount: conversations.length),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary, width: 2))),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConversationButton extends StatelessWidget {
|
||||
const ConversationButton({
|
||||
super.key,
|
||||
required this.model,
|
||||
});
|
||||
final ConversationModel model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
margin: const EdgeInsets.fromLTRB(themes.defaultSeperatorHeight * 2, 0,
|
||||
themes.defaultSeperatorHeight * 2, 0),
|
||||
width: global.getWidth(context),
|
||||
constraints: global.getConstraints(context),
|
||||
child: InkWell(
|
||||
child: Column(
|
||||
children: [
|
||||
Wrap(
|
||||
children: [SelectableText(model.identity)],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,147 +104,144 @@ class TimelineState extends State<Timeline> {
|
|||
));
|
||||
}
|
||||
selectedId = global.settings!.activeIdentity;
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 52,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: controller,
|
||||
itemBuilder: (context, index) {
|
||||
return children[index];
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
color: Colors.transparent,
|
||||
height: themes.defaultSeperatorHeight,
|
||||
);
|
||||
},
|
||||
itemCount: children.length,
|
||||
addAutomaticKeepAlives: false,
|
||||
),
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: controller,
|
||||
itemBuilder: (context, index) {
|
||||
return children[index];
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
color: Colors.transparent,
|
||||
height: themes.defaultSeperatorHeight,
|
||||
);
|
||||
},
|
||||
itemCount: children.length,
|
||||
addAutomaticKeepAlives: false,
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
)),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Wrap(
|
||||
spacing: 18,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
reload();
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
)),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Wrap(
|
||||
spacing: 18,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
reload();
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => showDialog(
|
||||
barrierColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => const SearchDialogue(),
|
||||
),
|
||||
icon: const Icon(Icons.search)),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton(
|
||||
alignment: Alignment.center,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
iconEnabledColor: Theme.of(context).colorScheme.onSurface,
|
||||
value: selectedId,
|
||||
items: identities,
|
||||
onChanged: (dynamic value) async {
|
||||
setState(() {
|
||||
selectedId = value ?? global.settings!.activeIdentity;
|
||||
global.settings!.saveActiveIdentity(selectedId);
|
||||
reload();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => showDialog(
|
||||
barrierColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => const SearchDialogue(),
|
||||
),
|
||||
icon: const Icon(Icons.search)),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton(
|
||||
alignment: Alignment.center,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
iconEnabledColor: Theme.of(context).colorScheme.onSurface,
|
||||
value: selectedId,
|
||||
items: identities,
|
||||
onChanged: (dynamic value) async {
|
||||
setState(() {
|
||||
selectedId = value ?? global.settings!.activeIdentity;
|
||||
global.settings!.saveActiveIdentity(selectedId);
|
||||
reload();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton(
|
||||
alignment: Alignment.center,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
iconEnabledColor: Theme.of(context).colorScheme.onSurface,
|
||||
value: selectedTimelineType,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
alignment: Alignment.center,
|
||||
value: tl.TimelineType.home,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
text: "${"home-timeline".i18n()} ",
|
||||
children: const [
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.home),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton(
|
||||
alignment: Alignment.center,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
iconEnabledColor: Theme.of(context).colorScheme.onSurface,
|
||||
value: selectedTimelineType,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
alignment: Alignment.center,
|
||||
value: tl.TimelineType.home,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
text: "${"home-timeline".i18n()} ",
|
||||
children: const [
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.home),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
alignment: Alignment.center,
|
||||
value: tl.TimelineType.local,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
text: "${"local-timeline".i18n()} ",
|
||||
children: const [
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.people),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
alignment: Alignment.center,
|
||||
value: tl.TimelineType.local,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
text: "${"local-timeline".i18n()} ",
|
||||
children: const [
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.people),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
alignment: Alignment.center,
|
||||
value: tl.TimelineType.public,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
text: "${"public-timeline".i18n()} ",
|
||||
children: const [
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.public),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
alignment: Alignment.center,
|
||||
value: tl.TimelineType.public,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
text: "${"public-timeline".i18n()} ",
|
||||
children: const [
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.public),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (tl.TimelineType? value) {
|
||||
setState(() {
|
||||
selectedTimelineType = value ?? tl.TimelineType.home;
|
||||
reload();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (tl.TimelineType? value) {
|
||||
setState(() {
|
||||
selectedTimelineType = value ?? tl.TimelineType.home;
|
||||
reload();
|
||||
});
|
||||
},
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: (() => showDialog(
|
||||
barrierColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => const MakePost(),
|
||||
)),
|
||||
icon: const Icon(Icons.create),
|
||||
label: Text("write".i18n())),
|
||||
],
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: (() => showDialog(
|
||||
barrierColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => const MakePost(),
|
||||
)),
|
||||
icon: const Icon(Icons.create),
|
||||
label: Text("write".i18n())),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,19 +41,10 @@ class _MainScaffoldState extends State<MainScaffold> {
|
|||
Widget build(BuildContext context) {
|
||||
final screens = [
|
||||
const Timeline(),
|
||||
chat(context),
|
||||
const Chat(),
|
||||
const Notifications(),
|
||||
settings(context),
|
||||
];
|
||||
final buttons = [
|
||||
null,
|
||||
FloatingActionButton(
|
||||
onPressed: () {},
|
||||
child: const Icon(Icons.person_add),
|
||||
),
|
||||
null,
|
||||
null,
|
||||
];
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
extendBody: false,
|
||||
|
@ -62,7 +53,6 @@ class _MainScaffoldState extends State<MainScaffold> {
|
|||
children: screens,
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||
floatingActionButton: buttons[index],
|
||||
bottomNavigationBar: SizedBox(
|
||||
height: 52,
|
||||
child: BottomAppBar(
|
||||
|
|
|
@ -111,6 +111,7 @@ class DisplayName extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
maxLines: 2,
|
||||
account.displayName,
|
||||
style: usernameStyle,
|
||||
),
|
||||
|
|
|
@ -27,13 +27,15 @@ class PostTextRenderer extends StatelessWidget {
|
|||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
a: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
blockquote: TextStyle(color: Theme.of(context).colorScheme.onBackground),
|
||||
blockquoteDecoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
style: BorderStyle.solid,
|
||||
)),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
style: BorderStyle.solid,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
String s = html2md.convert(input);
|
||||
|
|
|
@ -13,7 +13,7 @@ themes.CustomColors theme = themes.CustomColors(
|
|||
error: Color.fromARGB(255, 245, 248, 250),
|
||||
onError: Color.fromARGB(255, 20, 23, 26),
|
||||
background: Color.fromARGB(255, 0, 25, 53),
|
||||
onBackground: Color.fromARGB(255, 20, 23, 26),
|
||||
onBackground: Color.fromARGB(255, 255, 255, 255),
|
||||
surface: Color.fromARGB(255, 255, 255, 255),
|
||||
onSurface: Color.fromARGB(255, 0, 0, 0),
|
||||
),
|
||||
|
|
|
@ -96,7 +96,7 @@ ThemeData getTheme(CustomColors colors) {
|
|||
if (checkActive(states)) {
|
||||
return RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(defaultRadius),
|
||||
side: BorderSide(color: colors.colorScheme.primary),
|
||||
side: BorderSide(color: colors.colorScheme.primary, width: 2),
|
||||
);
|
||||
}
|
||||
return const RoundedRectangleBorder(
|
||||
|
|
Loading…
Reference in New Issue