ui: Make the UI much more consistent
This commit is contained in:
parent
be5a32dcf0
commit
35da08608b
@ -21,6 +21,10 @@ import "redux/conversation/actions.dart";
|
||||
import "redux/state.dart";
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
// TODO: Replace all single quotes with double quotes
|
||||
// TODO: Replace all Column(children: [ Padding(), Padding, ...]) with a
|
||||
// Padding(padding: ..., child: Column(children: [ ... ]))
|
||||
|
||||
void main() {
|
||||
GetIt.I.registerSingleton<RosterRepository>(RosterRepository());
|
||||
|
||||
|
@ -7,3 +7,9 @@ const Color BUBBLE_COLOR_SENT = Color.fromRGBO(162, 68, 173, 1.0);
|
||||
const Color BUBBLE_COLOR_RECEIVED = Color.fromRGBO(44, 62, 80, 1.0);
|
||||
|
||||
const double PADDING_VERY_LARGE = 64.0;
|
||||
|
||||
const double FONTSIZE_TITLE = 40;
|
||||
const double FONTSIZE_SUBTITLE = 25;
|
||||
const double FONTSIZE_APPBAR = 20;
|
||||
const double FONTSIZE_BODY = 15;
|
||||
const double FONTSIZE_SUBBODY = 10;
|
||||
|
@ -41,20 +41,7 @@ class AddContactPage extends StatelessWidget {TextEditingController controller =
|
||||
addContact: (jid) => store.dispatch(AddContactAction(jid: jid))
|
||||
),
|
||||
builder: (context, viewModel) => Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
BackButton(),
|
||||
Text(
|
||||
"Add new contact",
|
||||
style: TextStyle(
|
||||
fontSize: 19
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: BorderlessTopbar.simple(title: "Add new contact"),
|
||||
// TODO: The TextFields look a bit too smal
|
||||
// TODO: Hide the LinearProgressIndicator if we're not doing anything
|
||||
// TODO: Disable the inputs and the BackButton if we're working on loggin in
|
||||
|
@ -123,52 +123,16 @@ class ConversationPage extends StatelessWidget {
|
||||
),
|
||||
builder: (context, viewModel) {
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
boxShadow: true,
|
||||
children: [
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Icon(Icons.arrow_back)
|
||||
)
|
||||
),
|
||||
Center(
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16.0),
|
||||
child: AvatarWrapper(
|
||||
appBar: BorderlessTopbar.avatarAndName(
|
||||
avatar: AvatarWrapper(
|
||||
radius: 25.0,
|
||||
avatarUrl: viewModel.conversation.avatarUrl,
|
||||
altText: viewModel.conversation.title[0]
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 2.0),
|
||||
child: Text(
|
||||
viewModel.conversation.title,
|
||||
style: TextStyle(
|
||||
fontSize: 20
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, "/conversation/profile", arguments: ProfilePageArguments(conversation: viewModel.conversation, isSelfProfile: false));
|
||||
}
|
||||
)
|
||||
),
|
||||
Spacer(),
|
||||
// TODO
|
||||
// TODO: Make the icon depend on the current state
|
||||
// TODO: Gray-out if the contact does not support anything but plaintext
|
||||
// TODO: Use enum
|
||||
title: viewModel.conversation.title,
|
||||
onTapFunction: () {},
|
||||
showBackButton: true,
|
||||
extra: [
|
||||
PopupMenuButton(
|
||||
onSelected: (result) {
|
||||
if (result == "omemo") {
|
||||
@ -198,7 +162,6 @@ class ConversationPage extends StatelessWidget {
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
|
@ -27,33 +27,17 @@ class ConversationsPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext buildContext) {
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => Navigator.pushNamed(buildContext, "/conversation/profile", arguments: ProfilePageArguments(isSelfProfile: true)),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 3.0),
|
||||
child: AvatarWrapper(
|
||||
appBar: BorderlessTopbar.avatarAndName(
|
||||
avatar: AvatarWrapper(
|
||||
radius: 20.0,
|
||||
avatarUrl: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse3.mm.bing.net%2Fth%3Fid%3DOIP.MkXhyVPrn9eQGC1CTOyTYAHaHa%26pid%3DApi&f=1",
|
||||
// TODO
|
||||
altText: "?"
|
||||
)
|
||||
),
|
||||
Text(
|
||||
"Ojou",
|
||||
style: TextStyle(
|
||||
fontSize: 18
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
Spacer(),
|
||||
title: "Oujou",
|
||||
onTapFunction: () => Navigator.pushNamed(buildContext, "/conversation/profile", arguments: ProfilePageArguments(isSelfProfile: true)),
|
||||
showBackButton: false,
|
||||
extra: [
|
||||
PopupMenuButton(
|
||||
onSelected: (ConversationsOptions result) {
|
||||
if (result == ConversationsOptions.SETTINGS) {
|
||||
@ -70,7 +54,6 @@ class ConversationsPage extends StatelessWidget {
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
body: StoreConnector<MoxxyState, _ConversationsListViewModel>(
|
||||
converter: (store) => _ConversationsListViewModel(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moxxyv2/ui/widgets/topbar.dart';
|
||||
import "package:moxxyv2/ui/constants.dart";
|
||||
|
||||
class IntroPage extends StatelessWidget {
|
||||
@override
|
||||
@ -14,7 +15,7 @@ class IntroPage extends StatelessWidget {
|
||||
child: Text(
|
||||
"moxxy",
|
||||
style: TextStyle(
|
||||
fontSize: 40
|
||||
fontSize: FONTSIZE_TITLE
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -26,11 +27,11 @@ class IntroPage extends StatelessWidget {
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE),
|
||||
child: Text(
|
||||
"An experiment into building a modern, easy and beautiful XMPP client.",
|
||||
style: TextStyle(
|
||||
fontSize: 15
|
||||
fontSize: FONTSIZE_BODY
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -38,7 +39,7 @@ class IntroPage extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE),
|
||||
child: ElevatedButton(
|
||||
child: Text("Login"),
|
||||
onPressed: () => Navigator.pushNamed(context, "/login")
|
||||
@ -49,11 +50,11 @@ class IntroPage extends StatelessWidget {
|
||||
),
|
||||
Spacer(),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE),
|
||||
child: Text(
|
||||
"Have no XMPP account? No worries, creating one is really easy.",
|
||||
style: TextStyle(
|
||||
fontSize: 15
|
||||
fontSize: FONTSIZE_BODY
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -61,7 +62,7 @@ class IntroPage extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0).add(EdgeInsets.only(bottom: 64.0)),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(bottom: PADDING_VERY_LARGE)),
|
||||
child: TextButton(
|
||||
child: Text("Register"),
|
||||
onPressed: () => Navigator.pushNamed(context, "/register")
|
||||
|
@ -42,20 +42,7 @@ class LoginPage extends StatelessWidget {
|
||||
showPassword: store.state.loginPageState.showPassword
|
||||
),
|
||||
builder: (context, viewModel) => Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
BackButton(),
|
||||
Text(
|
||||
"Login",
|
||||
style: TextStyle(
|
||||
fontSize: 19
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: BorderlessTopbar.simple(title: "Login"),
|
||||
// TODO: The TextFields look a bit too smal
|
||||
// TODO: Hide the LinearProgressIndicator if we're not doing anything
|
||||
// TODO: Disable the inputs and the BackButton if we're working on loggin in
|
||||
|
@ -62,20 +62,7 @@ class NewConversationPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
var roster = GetIt.I.get<RosterRepository>().getAllRosterItems();
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
BackButton(),
|
||||
Text(
|
||||
"Start new chat",
|
||||
style: TextStyle(
|
||||
fontSize: 17
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: BorderlessTopbar.simple(title: "Start new chat"),
|
||||
body: StoreConnector<MoxxyState, _NewConversationViewModel>(
|
||||
converter: (store) => _NewConversationViewModel(
|
||||
addConversation: (c) => store.dispatch(
|
||||
|
@ -15,7 +15,7 @@ class PostRegistrationPage extends StatelessWidget {
|
||||
child: Text(
|
||||
"This is you!",
|
||||
style: TextStyle(
|
||||
fontSize: 40
|
||||
fontSize: FONTSIZE_TITLE
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -45,7 +45,7 @@ class PostRegistrationPage extends StatelessWidget {
|
||||
"Testuser",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 25
|
||||
fontSize: FONTSIZE_SUBTITLE
|
||||
)
|
||||
),
|
||||
Text("testuser@someprovider.net")
|
||||
@ -57,16 +57,16 @@ class PostRegistrationPage extends StatelessWidget {
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0).add(EdgeInsets.only(top: 16.0)),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 16.0)),
|
||||
child: Text(
|
||||
"We have auto-generated a password for you. You should write it down somewhere safe.",
|
||||
style: TextStyle(
|
||||
fontSize: 15
|
||||
fontSize: FONTSIZE_BODY
|
||||
)
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0).add(EdgeInsets.only(top: 16.0)),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 16.0)),
|
||||
child: ExpansionTile(
|
||||
title: Text("Show password"),
|
||||
children: [
|
||||
@ -75,7 +75,7 @@ class PostRegistrationPage extends StatelessWidget {
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE),
|
||||
child: ExpansionTile(
|
||||
title: Text("Advanced settings"),
|
||||
children: [
|
||||
@ -95,11 +95,16 @@ class PostRegistrationPage extends StatelessWidget {
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0).add(EdgeInsets.only(top: 16.0)),
|
||||
child: Text("You can now be contacted by your XMPP address. If you want to set a display name, just tap the text next to the profile picture.")
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 16.0)),
|
||||
child: Text(
|
||||
"You can now be contacted by your XMPP address. If you want to set a display name, just tap the text next to the profile picture.",
|
||||
style: TextStyle(
|
||||
fontSize: FONTSIZE_BODY
|
||||
)
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 64.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -59,20 +59,7 @@ class RegistrationPage extends StatelessWidget {
|
||||
performRegistration: () => store.dispatch(PerformRegistrationAction())
|
||||
),
|
||||
builder: (context, viewModel) => Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
BackButton(),
|
||||
Text(
|
||||
"Register",
|
||||
style: TextStyle(
|
||||
fontSize: 19
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: BorderlessTopbar.simple(title: "Register"),
|
||||
// TODO: The TextFields look a bit too smal
|
||||
// TODO: Hide the LinearProgressIndicator if we're not doing anything
|
||||
// TODO: Disable the inputs and the BackButton if we're working on loggin in
|
||||
@ -84,7 +71,12 @@ class RegistrationPage extends StatelessWidget {
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE, vertical: 16.0),
|
||||
child: Text("XMPP is a lot like e-mail: You can send e-mails to people who are not using your specific e-mail provider. As such, there are a lot of XMPP providers. To help you, we chose a random one from a curated list. You only have to pick a username.")
|
||||
child: Text(
|
||||
"XMPP is a lot like e-mail: You can send e-mails to people who are not using your specific e-mail provider. As such, there are a lot of XMPP providers. To help you, we chose a random one from a curated list. You only have to pick a username.",
|
||||
style: TextStyle(
|
||||
fontSize: FONTSIZE_BODY
|
||||
)
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(bottom: 8.0)),
|
||||
|
@ -17,14 +17,7 @@ class SettingsAboutPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
BackButton()
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: BorderlessTopbar.simple(title: "About"),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE),
|
||||
child: Column(
|
||||
|
@ -81,14 +81,7 @@ class SettingsLicensesPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
BackButton()
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: BorderlessTopbar.simple(title: "Licenses"),
|
||||
body: ListView.builder(
|
||||
itemCount: _USED_LIBRARIES.length,
|
||||
itemBuilder: (context, index) => LicenseRow(library: _USED_LIBRARIES[index])
|
||||
|
@ -8,20 +8,7 @@ class SettingsPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: BorderlessTopbar(
|
||||
children: [
|
||||
BackButton(),
|
||||
Text(
|
||||
"Settings",
|
||||
style: TextStyle(
|
||||
fontSize: 20
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
appBar: BorderlessTopbar.simple(title: "Settings"),
|
||||
body: SettingsList(
|
||||
// TODO: Seems hacky
|
||||
darkBackgroundColor: Color(0xff303030),
|
||||
|
@ -56,7 +56,7 @@ class ChatBubble extends StatelessWidget {
|
||||
this.messageContent,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17
|
||||
fontSize: FONTSIZE_BODY
|
||||
)
|
||||
),
|
||||
Row(
|
||||
@ -67,7 +67,7 @@ class ChatBubble extends StatelessWidget {
|
||||
child: Text(
|
||||
timestampString,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontSize: FONTSIZE_SUBBODY,
|
||||
color: Colors.grey
|
||||
)
|
||||
)
|
||||
|
@ -1,14 +1,71 @@
|
||||
import "dart:collection";
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import "package:moxxyv2/ui/widgets/avatar.dart";
|
||||
|
||||
/*
|
||||
Provides a Signal-like topbar without borders or anything else
|
||||
*/
|
||||
class BorderlessTopbar extends StatelessWidget {
|
||||
class BorderlessTopbar extends StatelessWidget implements PreferredSizeWidget {
|
||||
List<Widget> children;
|
||||
// TODO: Implement
|
||||
bool boxShadow;
|
||||
|
||||
BorderlessTopbar({ required this.children, this.boxShadow = false });
|
||||
|
||||
/*
|
||||
* A simple borderless topbar that displays just the back button (if wanted) and a
|
||||
* Text() title
|
||||
*/
|
||||
BorderlessTopbar.simple({ required String title , List<Widget>? extra, bool showBackButton = true }) : boxShadow = false, children = [
|
||||
Visibility(
|
||||
child: BackButton(),
|
||||
visible: showBackButton
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 20
|
||||
)
|
||||
),
|
||||
...(extra ?? [])
|
||||
];
|
||||
|
||||
/*
|
||||
* Displays a clickable avatar and title and a back button, if wanted
|
||||
*/
|
||||
// TODO: Reuse BorderlessTopbar.simple
|
||||
BorderlessTopbar.avatarAndName({ required AvatarWrapper avatar, required String title, void Function()? onTapFunction, List<Widget>? extra, bool showBackButton = true }) : boxShadow = false, children = [
|
||||
Visibility(
|
||||
child: BackButton(),
|
||||
visible: showBackButton
|
||||
),
|
||||
Center(
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
children: [
|
||||
avatar,
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 20
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
onTap: onTapFunction
|
||||
)
|
||||
),
|
||||
Spacer(),
|
||||
...(extra ?? [])
|
||||
];
|
||||
|
||||
@override
|
||||
final Size preferredSize = Size.fromHeight(60);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
|
Loading…
Reference in New Issue
Block a user