Merge pull request 'media-attachments' (#27) from media-attachments into main
Reviewed-on: #27
This commit is contained in:
commit
a60e144602
|
@ -0,0 +1,57 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:loris/global.dart' as global;
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
class FileUpload {
|
||||||
|
String description;
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
// media id for identity,
|
||||||
|
// gets set after first sucessfully uploading
|
||||||
|
Map<String, String> ids = {};
|
||||||
|
|
||||||
|
FileUpload({
|
||||||
|
required this.description,
|
||||||
|
required this.path,
|
||||||
|
this.ids = const {},
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<FileUpload> fromPath(String path, String description) async {
|
||||||
|
return FileUpload(
|
||||||
|
description: description,
|
||||||
|
path: path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
sends a media attachments and returns the http status code
|
||||||
|
if the status code is 200, the String is the media attachments id,
|
||||||
|
otherwise it's the error response
|
||||||
|
*/
|
||||||
|
Future<MapEntry<int, String>> upload(String identityName) async {
|
||||||
|
final identity = global.settings!.identities[identityName]!;
|
||||||
|
final headers = {
|
||||||
|
...global.defaultHeaders,
|
||||||
|
...identity.getAuthHeaders(),
|
||||||
|
};
|
||||||
|
final uri =
|
||||||
|
Uri(scheme: "https", host: identity.instanceUrl, path: "/api/v1/media");
|
||||||
|
final request = MultipartRequest(
|
||||||
|
"POST",
|
||||||
|
uri,
|
||||||
|
);
|
||||||
|
request.headers.addAll(headers);
|
||||||
|
request.files.add(await MultipartFile.fromPath(
|
||||||
|
"file",
|
||||||
|
path,
|
||||||
|
));
|
||||||
|
request.fields.addAll({"description": description});
|
||||||
|
final response = await http.Response.fromStream(await request.send());
|
||||||
|
if (response.statusCode != 200 && response.statusCode != 202) {
|
||||||
|
return MapEntry(response.statusCode, jsonDecode(response.body)["error"]);
|
||||||
|
}
|
||||||
|
return MapEntry(response.statusCode, jsonDecode(response.body)["id"]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,11 +29,13 @@ class InstanceConfiguration {
|
||||||
|
|
||||||
class StatusConfiguration {
|
class StatusConfiguration {
|
||||||
final int maxChars;
|
final int maxChars;
|
||||||
|
final int maxMediaAttachments;
|
||||||
|
|
||||||
StatusConfiguration(this.maxChars);
|
StatusConfiguration(this.maxChars, this.maxMediaAttachments);
|
||||||
static StatusConfiguration fromJson(Map<String, dynamic> json) {
|
static StatusConfiguration fromJson(Map<String, dynamic> json) {
|
||||||
return StatusConfiguration(
|
return StatusConfiguration(
|
||||||
json["max_characters"],
|
json["max_characters"],
|
||||||
|
json["max_media_attachments"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,19 @@ class MakePostModel {
|
||||||
final Visibility visibility;
|
final Visibility visibility;
|
||||||
final String? scheduledAt;
|
final String? scheduledAt;
|
||||||
final String? inReplyToId;
|
final String? inReplyToId;
|
||||||
|
List<String> mediaIds;
|
||||||
|
|
||||||
MakePostModel({
|
MakePostModel({
|
||||||
required this.identity,
|
required this.identity,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.spoilerText,
|
required this.spoilerText,
|
||||||
required this.visibility,
|
required this.visibility,
|
||||||
|
required this.mediaIds,
|
||||||
this.scheduledAt,
|
this.scheduledAt,
|
||||||
this.inReplyToId,
|
this.inReplyToId,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<int> sendPost() async {
|
Future<http.Response> sendPost() async {
|
||||||
final headers = global.settings!.identities[identity]!.getAuthHeaders();
|
final headers = global.settings!.identities[identity]!.getAuthHeaders();
|
||||||
headers.addAll(global.defaultHeaders);
|
headers.addAll(global.defaultHeaders);
|
||||||
|
|
||||||
|
@ -29,6 +31,7 @@ class MakePostModel {
|
||||||
"status": status,
|
"status": status,
|
||||||
"sensitive": false,
|
"sensitive": false,
|
||||||
"visibility": visibility.queryParam,
|
"visibility": visibility.queryParam,
|
||||||
|
"media_ids": mediaIds,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (inReplyToId != null) {
|
if (inReplyToId != null) {
|
||||||
|
@ -55,6 +58,6 @@ class MakePostModel {
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: jsonEncode(params),
|
body: jsonEncode(params),
|
||||||
);
|
);
|
||||||
return response.statusCode;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:localization/localization.dart';
|
import 'package:localization/localization.dart';
|
||||||
|
import 'package:loris/business_logic/fileupload/fileupload.dart';
|
||||||
import 'package:loris/business_logic/instance/instance.dart';
|
import 'package:loris/business_logic/instance/instance.dart';
|
||||||
import 'package:loris/business_logic/network_tools/get_post_from_url.dart';
|
import 'package:loris/business_logic/network_tools/get_post_from_url.dart';
|
||||||
import 'package:loris/business_logic/timeline/timeline.dart' as tl;
|
import 'package:loris/business_logic/timeline/timeline.dart' as tl;
|
||||||
import '../business_logic/posting/posting.dart';
|
import '../business_logic/posting/posting.dart';
|
||||||
import '../partials/post.dart';
|
import '../partials/post.dart';
|
||||||
import 'package:loris/global.dart' as global;
|
import 'package:loris/global.dart' as global;
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:universal_io/io.dart' as io;
|
||||||
|
|
||||||
class MakePost extends StatefulWidget {
|
class MakePost extends StatefulWidget {
|
||||||
const MakePost({Key? key, this.inReplyTo}) : super(key: key);
|
const MakePost({Key? key, this.inReplyTo}) : super(key: key);
|
||||||
|
@ -18,26 +24,59 @@ class MakePost extends StatefulWidget {
|
||||||
class _MakePostState extends State<MakePost> {
|
class _MakePostState extends State<MakePost> {
|
||||||
String replyAts = "";
|
String replyAts = "";
|
||||||
String accountid = global.settings!.activeIdentity;
|
String accountid = global.settings!.activeIdentity;
|
||||||
int? maxLength;
|
InstanceInformation? instanceInfo;
|
||||||
String text = "";
|
String text = "";
|
||||||
String spoilerText = "";
|
String spoilerText = "";
|
||||||
|
String? status;
|
||||||
|
bool sending = false;
|
||||||
|
// tracks fileuploads and their ids on servers
|
||||||
|
// if the id is null the file has not been uploaded yet
|
||||||
|
List<FileUpload> files = [];
|
||||||
// stores all identities and if available the post id you are replying to
|
// stores all identities and if available the post id you are replying to
|
||||||
Map<String, String?> identitiesAvailable = {};
|
Map<String, String?> identitiesAvailable = {};
|
||||||
tl.Visibility visibility = tl.Visibility.public;
|
tl.Visibility visibility = tl.Visibility.public;
|
||||||
|
|
||||||
void switchAccount(String acct) {
|
void switchAccount(String acct) {
|
||||||
setState(() {
|
setState(() {
|
||||||
maxLength = null;
|
instanceInfo = null;
|
||||||
accountid = acct;
|
accountid = acct;
|
||||||
});
|
});
|
||||||
updateMaxChars();
|
updateMaxChars();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if there are more attachments than the instance ur posting to allowss
|
||||||
|
bool tooManyAttachments() {
|
||||||
|
if (instanceInfo == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (instanceInfo!.configuration.statusconfig.maxMediaAttachments <
|
||||||
|
files.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// moves file in list up or down,
|
||||||
|
// has to be called with file from items list
|
||||||
|
void moveFile(FileUpload file, {up = true}) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final index = files.indexOf(file);
|
||||||
|
final newIndex = (index + (up ? -1 : 1)) % files.length;
|
||||||
|
|
||||||
|
files.remove(file);
|
||||||
|
setState(() {
|
||||||
|
files.insert(newIndex, file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void updateMaxChars() async {
|
void updateMaxChars() async {
|
||||||
final info = await instanceInformationForIdentity(accountid);
|
final info = await instanceInformationForIdentity(accountid);
|
||||||
if (info.keys.first == 200) {
|
if (info.keys.first == 200 && mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
maxLength = info.values.first!.configuration.statusconfig.maxChars;
|
instanceInfo = info.values.first!;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,6 +139,7 @@ class _MakePostState extends State<MakePost> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// make list of all different widgets to be displayed
|
||||||
List<Widget> c = [];
|
List<Widget> c = [];
|
||||||
if (widget.inReplyTo != null) {
|
if (widget.inReplyTo != null) {
|
||||||
c.add(
|
c.add(
|
||||||
|
@ -140,9 +180,6 @@ class _MakePostState extends State<MakePost> {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
List<Widget> actionButtons = [
|
List<Widget> actionButtons = [
|
||||||
maxLength == null
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: SelectableText((maxLength! - text.length).toString()),
|
|
||||||
DropdownButtonHideUnderline(
|
DropdownButtonHideUnderline(
|
||||||
child: DropdownButton<tl.Visibility>(
|
child: DropdownButton<tl.Visibility>(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
|
@ -156,6 +193,31 @@ class _MakePostState extends State<MakePost> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// media attachment button
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
// open filepicker
|
||||||
|
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
|
withData: false,
|
||||||
|
allowMultiple: true,
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
// if there are any files
|
||||||
|
// then parse them
|
||||||
|
for (var path in result.paths) {
|
||||||
|
if (path != null && mounted) {
|
||||||
|
if (await io.Directory(path).exists()) break;
|
||||||
|
final FileUpload f = await FileUpload.fromPath(path, "");
|
||||||
|
setState(() {
|
||||||
|
files.add(f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.attachment),
|
||||||
|
label: Text(
|
||||||
|
"${"add-files".i18n()}${instanceInfo == null ? "(${"loading...".i18n()})" : " (${instanceInfo!.configuration.statusconfig.maxMediaAttachments - files.length} ${"remaining".i18n()})"}")),
|
||||||
DropdownButtonHideUnderline(
|
DropdownButtonHideUnderline(
|
||||||
child: DropdownButton<String>(
|
child: DropdownButton<String>(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
|
@ -173,19 +235,59 @@ class _MakePostState extends State<MakePost> {
|
||||||
"send-post".i18n(),
|
"send-post".i18n(),
|
||||||
),
|
),
|
||||||
// send the post!!!
|
// send the post!!!
|
||||||
onPressed: () async {
|
onPressed: ((spoilerText.isEmpty && text.isEmpty && files.isEmpty) ||
|
||||||
final model = MakePostModel(
|
tooManyAttachments())
|
||||||
spoilerText: spoilerText.trim(),
|
? null
|
||||||
identity: accountid,
|
: () async {
|
||||||
status: text,
|
setState(() {
|
||||||
visibility: visibility,
|
sending = true;
|
||||||
inReplyToId: widget.inReplyTo == null
|
status = "sending...".i18n();
|
||||||
? null
|
});
|
||||||
: identitiesAvailable[accountid]!,
|
|
||||||
);
|
// upload the media attachments
|
||||||
model.sendPost();
|
List<String> mediaIds = [];
|
||||||
Navigator.of(context).pop();
|
for (var file in files) {
|
||||||
},
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
status =
|
||||||
|
"${"sending-file".i18n()} ${mediaIds.length + 1}/${files.length}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
final response = await file.upload(accountid);
|
||||||
|
if (response.key == 200 || response.key == 202) {
|
||||||
|
mediaIds.add(response.value);
|
||||||
|
} else if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
status = response.value;
|
||||||
|
sending = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the final post
|
||||||
|
final model = MakePostModel(
|
||||||
|
mediaIds: mediaIds,
|
||||||
|
spoilerText: spoilerText.trim(),
|
||||||
|
identity: accountid,
|
||||||
|
status: text,
|
||||||
|
visibility: visibility,
|
||||||
|
inReplyToId: widget.inReplyTo == null
|
||||||
|
? null
|
||||||
|
: identitiesAvailable[accountid]!,
|
||||||
|
);
|
||||||
|
final result = await model.sendPost();
|
||||||
|
if (mounted) {
|
||||||
|
if (result.statusCode == 200) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
sending = false;
|
||||||
|
status =
|
||||||
|
"${"failed-to-send".i18n()}: ${jsonDecode(result.body)["error"].toString()}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -194,6 +296,12 @@ class _MakePostState extends State<MakePost> {
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
initialValue: spoilerText,
|
initialValue: spoilerText,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
counterText: instanceInfo == null
|
||||||
|
? "loading...".i18n()
|
||||||
|
: (instanceInfo!.configuration.statusconfig.maxChars -
|
||||||
|
text.length -
|
||||||
|
spoilerText.length)
|
||||||
|
.toString(),
|
||||||
prefixIcon: Icon(
|
prefixIcon: Icon(
|
||||||
Icons.warning,
|
Icons.warning,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
@ -221,6 +329,10 @@ class _MakePostState extends State<MakePost> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 24,
|
height: 24,
|
||||||
),
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// these are the action buttons
|
||||||
|
c.add(
|
||||||
Wrap(
|
Wrap(
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
spacing: 24,
|
spacing: 24,
|
||||||
|
@ -229,25 +341,76 @@ class _MakePostState extends State<MakePost> {
|
||||||
alignment: WrapAlignment.spaceAround,
|
alignment: WrapAlignment.spaceAround,
|
||||||
children: actionButtons,
|
children: actionButtons,
|
||||||
),
|
),
|
||||||
]);
|
);
|
||||||
|
|
||||||
|
// display media attachments
|
||||||
|
for (var file in files) {
|
||||||
|
c.add(Column(
|
||||||
|
children: [
|
||||||
|
FileUploadDisplay(file: file),
|
||||||
|
TextField(
|
||||||
|
onChanged: (value) => file.description = value,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "file-description".i18n(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
moveFile(file, up: false);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.move_down),
|
||||||
|
label: Text("move-down".i18n()),
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
files.remove(file);
|
||||||
|
}),
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
label: Text(
|
||||||
|
"remove-attached-file".i18n(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
moveFile(file);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.move_up),
|
||||||
|
label: Text("move-up".i18n()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
|
titlePadding: const EdgeInsets.all(0),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
contentPadding: const EdgeInsets.all(24),
|
contentPadding: const EdgeInsets.all(24),
|
||||||
title: SelectableText(
|
title: Column(
|
||||||
textAlign: TextAlign.center,
|
children: [
|
||||||
widget.inReplyTo == null ? "make-post".i18n() : "make-reply".i18n(),
|
SelectableText(
|
||||||
style: Theme.of(context).textTheme.displayMedium),
|
textAlign: TextAlign.center,
|
||||||
|
widget.inReplyTo == null
|
||||||
|
? "make-post".i18n()
|
||||||
|
: "make-reply".i18n(),
|
||||||
|
style: Theme.of(context).textTheme.displayMedium),
|
||||||
|
if (status != null) SelectableText(status!),
|
||||||
|
if (sending) const LinearProgressIndicator(),
|
||||||
|
],
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: (MediaQuery.of(context).size.width *
|
width: (MediaQuery.of(context).size.width *
|
||||||
global.settings!.postWidth) -
|
global.settings!.postWidth) -
|
||||||
56,
|
56,
|
||||||
constraints: BoxConstraints(
|
constraints: global.getConstraints(context),
|
||||||
maxWidth: global.settings!.maxPostWidth,
|
|
||||||
minWidth: 375,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
@ -259,3 +422,35 @@ class _MakePostState extends State<MakePost> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FileUploadDisplay extends StatelessWidget {
|
||||||
|
const FileUploadDisplay({super.key, required this.file});
|
||||||
|
final FileUpload file;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Divider(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.attachment, size: 32),
|
||||||
|
Expanded(
|
||||||
|
child: SelectableText(file.path,
|
||||||
|
style: Theme.of(context).textTheme.displaySmall),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (lookupMimeType(file.path)!.startsWith("image/"))
|
||||||
|
Image.asset(
|
||||||
|
file.path,
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -69,8 +69,9 @@ class _SearchDialogueState extends State<SearchDialogue> {
|
||||||
onChanged: ((value) => setState(() {
|
onChanged: ((value) => setState(() {
|
||||||
searchText = value;
|
searchText = value;
|
||||||
})),
|
})),
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: Icon(
|
prefixIcon: Icon(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
Icons.search,
|
Icons.search,
|
||||||
))),
|
))),
|
||||||
if (searched < global.settings!.identities.length &&
|
if (searched < global.settings!.identities.length &&
|
||||||
|
|
|
@ -73,6 +73,17 @@
|
||||||
"instances": "instances",
|
"instances": "instances",
|
||||||
"instance": "instace",
|
"instance": "instace",
|
||||||
"post-found-on": "found post on",
|
"post-found-on": "found post on",
|
||||||
"show-in-full": "show in full"
|
"show-in-full": "show in full",
|
||||||
|
"block": "banish",
|
||||||
|
"unblock": "unbanish",
|
||||||
|
"add-files": "add files",
|
||||||
|
"remaining": "remaining",
|
||||||
|
"move-down": "move down",
|
||||||
|
"move-up": "move up",
|
||||||
|
"remove-attached-file": "remove attachment",
|
||||||
|
"file-description": "file description",
|
||||||
|
"loading...": "loading",
|
||||||
|
"failed-to-send": "failed to send!",
|
||||||
|
"sending-file": "sending file"
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@ final available = [
|
||||||
|
|
||||||
ThemeData getTheme(CustomColors colors) {
|
ThemeData getTheme(CustomColors colors) {
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||||
|
hoverColor: colors.colorScheme.onSurface,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
enableFeedback: false,
|
enableFeedback: false,
|
||||||
hoverElevation: 24,
|
hoverElevation: 24,
|
||||||
|
@ -75,13 +76,22 @@ ThemeData getTheme(CustomColors colors) {
|
||||||
color: colors.colorScheme.onSurface,
|
color: colors.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ButtonStyle(
|
||||||
foregroundColor: colors.colorScheme.onPrimary,
|
foregroundColor: MaterialStateProperty.resolveWith((states) {
|
||||||
textStyle: const TextStyle(
|
if (states.contains(MaterialState.disabled)) {
|
||||||
fontFamily: 'Atkinson',
|
return colors.colorScheme.surface;
|
||||||
fontSize: 18,
|
}
|
||||||
fontWeight: FontWeight.w700,
|
return null;
|
||||||
),
|
}),
|
||||||
|
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) return colors.hintColor;
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
textStyle: MaterialStateProperty.resolveWith((states) =>
|
||||||
|
const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: "atkinson",
|
||||||
|
fontWeight: FontWeight.w700)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
|
@ -120,8 +130,6 @@ ThemeData getTheme(CustomColors colors) {
|
||||||
errorColor: colors.colorScheme.error,
|
errorColor: colors.colorScheme.error,
|
||||||
bottomAppBarTheme: BottomAppBarTheme(
|
bottomAppBarTheme: BottomAppBarTheme(
|
||||||
color: colors.colorScheme.surface,
|
color: colors.colorScheme.surface,
|
||||||
shape: const CircularNotchedRectangle(),
|
|
||||||
elevation: 0,
|
|
||||||
),
|
),
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
|
@ -172,6 +180,7 @@ ThemeData getTheme(CustomColors colors) {
|
||||||
unselectedWidgetColor: colors.hintColor,
|
unselectedWidgetColor: colors.hintColor,
|
||||||
toggleableActiveColor: colors.colorScheme.primary,
|
toggleableActiveColor: colors.colorScheme.primary,
|
||||||
splashColor: colors.colorScheme.onSurface,
|
splashColor: colors.colorScheme.onSurface,
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
highlightColor: colors.hintColor,
|
highlightColor: colors.hintColor,
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
helperStyle: TextStyle(
|
helperStyle: TextStyle(
|
||||||
|
|
35
pubspec.lock
35
pubspec.lock
|
@ -99,6 +99,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.1.4"
|
||||||
|
file_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -122,7 +129,14 @@ packages:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.10+5"
|
version: "0.6.10+6"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.7"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -217,6 +231,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
|
mime:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -244,7 +265,7 @@ packages:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.3"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -258,7 +279,7 @@ packages:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.3"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -279,7 +300,7 @@ packages:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.12"
|
version: "2.0.13"
|
||||||
shared_preferences_ios:
|
shared_preferences_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -328,7 +349,7 @@ packages:
|
||||||
name: shelf
|
name: shelf
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.4.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -410,7 +431,7 @@ packages:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.17"
|
version: "6.0.19"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -480,7 +501,7 @@ packages:
|
||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "3.0.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -51,6 +51,8 @@ dependencies:
|
||||||
url_strategy: ^0.2.0
|
url_strategy: ^0.2.0
|
||||||
universal_html: ^2.0.8
|
universal_html: ^2.0.8
|
||||||
universal_io: ^2.0.4
|
universal_io: ^2.0.4
|
||||||
|
file_picker: ^5.2.0
|
||||||
|
mime: ^1.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue