start dm feature

This commit is contained in:
zoe 2022-09-27 21:49:11 +02:00
parent aa46264c64
commit a73614b5cc
9 changed files with 316 additions and 153 deletions

View File

@ -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());
}

View File

@ -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,

View File

@ -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)],
)
],
),
),
);
}
}

View File

@ -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())),
],
),
),
],
),
),
],
);
}

View File

@ -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(

View File

@ -111,6 +111,7 @@ class DisplayName extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
maxLines: 2,
account.displayName,
style: usernameStyle,
),

View File

@ -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);

View File

@ -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),
),

View File

@ -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(