Compare commits

...

5 Commits

Author SHA1 Message Date
zoe 30de8189bc rename to loris 2022-07-04 19:37:53 +02:00
zoe 05eef7f783 warn the contents (kinda) 2022-07-03 22:02:57 +02:00
zoe e4a56146d5 post dialogue 2022-07-03 16:56:41 +02:00
zoe ee081de0ab scaling the timeline 2022-07-03 15:47:24 +02:00
zoe c63f062640 theming!!! 2022-07-03 00:03:10 +02:00
22 changed files with 485 additions and 117 deletions

View File

@ -1,4 +1,7 @@
import 'package:flutter/painting.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../global.dart' as global;
enum Settings {
instanceUrl,
@ -38,3 +41,20 @@ Future<String> loadAuthCode() async {
}
return code;
}
Future<bool> saveLocale(String locale) async {
final prefs = await SharedPreferences.getInstance();
return await prefs.setString("active-locale", locale);
}
Future<Locale> loadLocale() async {
final prefs = await SharedPreferences.getInstance();
String? locale = prefs.getString("active-locale");
if (locale == null) {
if (global.availableLocales.contains(Locale(Intl.systemLocale))) {
return Locale(Intl.systemLocale);
}
return const Locale("en");
}
return Locale(locale);
}

View File

@ -0,0 +1,7 @@
import 'package:http/http.dart' as http;
class Thread {}
class Post {}
class Timeline {}

View File

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
class MakePost extends StatelessWidget {
const MakePost({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SimpleDialog(
backgroundColor: Theme.of(context).colorScheme.background,
elevation: 0,
contentPadding: const EdgeInsets.all(24),
insetPadding: const EdgeInsets.all(24),
children: [
TextFormField(
autofocus: true,
keyboardType: TextInputType.multiline,
minLines: 4,
maxLines: null,
),
const MakePostActionBar(),
],
);
}
}
class MakePostActionBar extends StatelessWidget {
const MakePostActionBar({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(Icons.cancel),
),
],
);
}
}

View File

@ -1,4 +1,6 @@
const String name = "slothmu";
import 'package:flutter/painting.dart';
const String name = "loris";
const String version = "v0.1 'not even alpha'";
const String useragent = "$name/$version";
const String website = "https://git.kittycat.homes/zoe/slothmu";
@ -8,3 +10,4 @@ const Map<String, String> defaultHeaders = {
"Content-Type": "application/json"
};
const List<String> bad = ["gab.com", "spinster.xyz", "truthsocial.com"];
const List<Locale> availableLocales = [Locale("en"), Locale("de")];

16
lib/i18n/de.json Normal file
View File

@ -0,0 +1,16 @@
{
"greeting": "hallo!",
"instance-url": "anbieter url",
"instance-url-example": "beispiel.de",
"authorize-in-browser": "im browser autorisieren",
"login-failed-snackbar-text": "login fehlgeschlagen!",
"back-button": "zurück",
"confirm-button": "bestätigen",
"timeline" : "timeline",
"chat": "chat",
"notifications": "benachrichtigungen",
"settings": "einstellungen",
"show": "zeigen",
"hide": "verstecken"
}

View File

@ -1,13 +0,0 @@
{
"greeting": "hallo!",
"user-id-not-valid": "hmm... die id sieht nicht ganz richtig aus...",
"user-id": "nutzer id",
"user-id-example": "nutzer@beispiel.de",
"authorize-in-browser": "im browser autorisieren",
"login-failed-snackbar-text": "login fehlgeschlagen!",
"back-button": "zurück",
"confirm-button": "bestätigen",
"copy-code-from-browser": "bitte den code aus dem browser hierhin kopieren.",
"code-hint": "code"
}

16
lib/i18n/en.json Normal file
View File

@ -0,0 +1,16 @@
{
"greeting": "hello!",
"instance-url": "instance url",
"instance-url-example": "example.com",
"authorize-in-browser": "authorize in browser",
"login-failed-snackbar-text": "login failed!",
"back-button": "back",
"confirm-button": "confirm",
"timeline" : "timeline",
"chat": "chat",
"notifications": "notifications",
"settings": "settings",
"show": "show",
"hide": "hide"
}

View File

@ -1,13 +0,0 @@
{
"greeting": "hello!",
"user-id-not-valid": "sorry, this user id doesn't look quite right... ",
"user-id": "user id",
"user-id-example": "user@example.com",
"authorize-in-browser": "authorize in browser",
"login-failed-snackbar-text": "login failed!",
"back-button": "back",
"confirm-button": "confirm",
"copy-code-from-browser": "please copy the code from your browser here!",
"code-hint": "code"
}

View File

@ -6,16 +6,20 @@ import 'pages/login.dart';
import 'business_logic/settings.dart' as settings;
import 'package:flutter_localizations/flutter_localizations.dart';
import 'themes/themes.dart' as themes;
import 'global.dart' as global;
String _initRoute = "/";
ThemeData theme = themes.getTheme(themes.available[0]);
Locale activeLocale = const Locale("en");
void main() async {
Intl.defaultLocale = 'en_US';
Intl.defaultLocale = "en";
await settings.saveLocale("en");
activeLocale = await settings.loadLocale();
// check if all information is available
settings.saveAuthCode("");
if (await settings.loadAuthCode() == "") {
_initRoute = "/login";
_initRoute = "/";
}
runApp(const Slothmu());
}
@ -28,16 +32,13 @@ class Slothmu extends StatefulWidget {
}
class _SlothmuState extends State<Slothmu> {
List<Locale> supported = const [
Locale("en", "US"),
Locale("de", "DE"),
];
@override
Widget build(BuildContext context) {
LocalJsonLocalization.delegate.directories = ['lib/i18n'];
return MaterialApp(
theme: themes.defaultThemeDark,
supportedLocales: supported,
theme: theme,
locale: activeLocale,
supportedLocales: global.availableLocales,
localizationsDelegates: [
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,

View File

@ -1,5 +0,0 @@
import 'package:flutter/widgets.dart';
Widget Chat() {
return Center(child: Text("Chat"));
}

5
lib/pages/chat/chat.dart Normal file
View File

@ -0,0 +1,5 @@
import 'package:flutter/widgets.dart';
Widget chat(context) {
return const Center(child: Text("Chat"));
}

View File

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:slothmu/business_logic/user.dart';
import 'package:localization/localization.dart';
import '../business_logic/auth/oauth.dart' as oauth;
import '../business_logic/settings.dart' as settings;
import '../business_logic/user.dart' as user;
class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key);
@ -44,26 +42,18 @@ class _LoginFormState extends State<LoginForm> {
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text("greeting".i18n(), style: const TextStyle(fontSize: 64)),
Text("greeting".i18n(), style: Theme.of(context).textTheme.headline1),
TextFormField(
onSaved: (value) async {
await settings
.saveInstanceUrl(user.urlFromUsername(name: value!));
await settings.saveUsername(user.userFromUsername(name: value));
await settings.saveInstanceUrl(value!);
},
decoration: InputDecoration(
labelText: "user-id".i18n(),
hintText: "user-id-example".i18n(),
icon: const Icon(Icons.person),
prefixText: "@",
labelText: "instance-url".i18n(),
hintText: "instance-url-example".i18n(),
icon: const Icon(Icons.home),
prefixText: "https://",
),
autofocus: true,
validator: (value) {
if (value!.isEmpty || !isValidUsername(name: value)) {
return "user-id-not-valid".i18n();
}
return null;
},
),
TextButton.icon(
onPressed: () {
@ -76,8 +66,7 @@ class _LoginFormState extends State<LoginForm> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AuthPage(baseurl: "kittycat.homes"),
builder: (context) => const AuthPage(),
));
}
},
@ -93,7 +82,7 @@ class _LoginFormState extends State<LoginForm> {
page that handles authenticating user
*/
class AuthPage extends StatefulWidget {
const AuthPage({Key? key, required String baseurl}) : super(key: key);
const AuthPage({Key? key}) : super(key: key);
@override
State<AuthPage> createState() => _AuthPageState();

View File

@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
Widget notifications(context) {
return const Center(child: Text("Notifications"));
}

View File

@ -1,15 +1,7 @@
import 'package:flutter/material.dart';
import 'package:settings_ui/settings_ui.dart';
Widget Settings() {
return SafeArea(
child: SettingsList(contentPadding: EdgeInsets.all(24), sections: [
SettingsSection(title: Text("General"), tiles: [
SettingsTile(
title: Text("Language"),
value: Text("no"),
)
])
]),
Widget settings(context) {
return const Center(
child: Text("Settings"),
);
}

View File

@ -1,5 +1,80 @@
import 'package:flutter/material.dart';
import 'package:localization/localization.dart';
import '../../business_logic/settings.dart' as settings;
import 'package:slothmu/partials/thread.dart';
Widget Timeline() {
return Center(child: Text("Home"));
class Timeline extends StatefulWidget {
const Timeline({Key? key}) : super(key: key);
@override
State<Timeline> createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
final controller = ScrollController();
List<Widget> children = [];
bool loading = false;
@override
void initState() {
super.initState();
fetchMore();
controller.addListener(() {
if (controller.position.maxScrollExtent <= controller.offset &&
!loading) {
fetchMore();
}
});
}
Future fetchMore() async {
loading = true;
final token = await settings.loadAuthCode();
setState(() {
if (children.isNotEmpty) {
children.removeAt(children.length - 1);
}
children.addAll([Thread()]);
children.add(
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton.icon(
onPressed: () {
fetchMore();
},
icon: const Icon(Icons.more_horiz),
label: Text("load-more".i18n()),
)
],
),
);
loading = false;
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
controller: controller,
itemBuilder: (context, index) {
return children[index];
},
separatorBuilder: (context, index) {
return const Divider(
color: Colors.transparent,
);
},
itemCount: children.length,
padding: const EdgeInsets.fromLTRB(24, 0, 24, 64),
addAutomaticKeepAlives: false,
);
}
}

View File

@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:slothmu/pages/chat/%20chat.dart';
import 'package:localization/localization.dart';
import 'package:slothmu/dialogues/makepost.dart';
import 'package:slothmu/pages/chat/chat.dart';
import 'package:slothmu/pages/notifications/notifications.dart';
import 'package:slothmu/pages/timeline/timeline.dart';
import 'package:slothmu/pages/settings/settings.dart';
@ -12,29 +15,55 @@ class MainScaffold extends StatefulWidget {
class _MainScaffoldState extends State<MainScaffold> {
int index = 0;
final screens = [
Timeline(),
Chat(),
Settings(),
];
@override
Widget build(BuildContext context) {
final screens = [
const Timeline(),
chat(context),
notifications(context),
settings(context),
];
final buttons = [
FloatingActionButton(
child: const Icon(Icons.create),
onPressed: () => showDialog(
context: context,
builder: (context) => const MakePost(),
),
),
FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.person_add),
),
FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.clear_all),
),
null,
];
return Scaffold(
extendBody: true,
body: screens[index],
bottomNavigationBar: NavigationBar(
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
elevation: 0,
height: 60,
onDestinationSelected: (index) => setState(() => this.index = index),
selectedIndex: index,
destinations: const [
NavigationDestination(
icon: Icon(Icons.list_alt), label: "Timeline"),
NavigationDestination(icon: Icon(Icons.chat), label: "Chat"),
NavigationDestination(
icon: Icon(Icons.settings), label: "Settings"),
]),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: buttons[index],
bottomNavigationBar: BottomAppBar(
child: NavigationBar(
onDestinationSelected: (index) =>
setState(() => this.index = index),
selectedIndex: index,
destinations: [
NavigationDestination(
icon: const Icon(Icons.forum), label: "timeline".i18n()),
NavigationDestination(
icon: const Icon(Icons.chat), label: "chat".i18n()),
NavigationDestination(
icon: const Icon(Icons.notifications),
label: "notifications".i18n()),
NavigationDestination(
icon: const Icon(Icons.settings), label: "settings".i18n()),
]),
),
);
}
}

130
lib/partials/post.dart Normal file
View File

@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:localization/localization.dart';
class Post extends StatefulWidget {
const Post({Key? key}) : super(key: key);
@override
State<Post> createState() => _PostState();
}
class _PostState extends State<Post> {
@override
Widget build(BuildContext context) {
return Column(
children: [
const DisplayName(),
const PostBody(),
postActionBar(context),
],
);
}
}
class DisplayName extends StatelessWidget {
const DisplayName({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
const Icon(
Icons.face,
size: 64,
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"first name display last name name",
style: Theme.of(context).textTheme.titleMedium,
),
Text(
"@alice_exampleuser@example.com",
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
);
}
}
class PostBody extends StatefulWidget {
const PostBody({Key? key}) : super(key: key);
@override
State<PostBody> createState() => _PostBodyState();
}
class _PostBodyState extends State<PostBody> {
bool visible = false;
String cwButtonText = "show".i18n();
Icon cwButtonIcon = const Icon(Icons.visibility);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 6, 0, 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"sdkfjksdjfkd sldkjfksdjf dskfjsdkfjkds skdfjksdjfisdujfiosdhfjkldsfh sldkfjksdjfksdjfklsdjf"),
OutlinedButton.icon(
onPressed: () {
setState(() {
visible = !visible;
if (visible) {
cwButtonIcon = const Icon(Icons.visibility_off);
cwButtonText = "hide".i18n();
} else {
cwButtonText = "show".i18n();
cwButtonIcon = const Icon(Icons.visibility);
}
});
},
icon: cwButtonIcon,
label: Text(cwButtonText)),
Visibility(
visible: visible,
child: RichText(
textAlign: TextAlign.start,
text: TextSpan(
style: Theme.of(context).textTheme.bodyMedium,
text:
"Lorem ipsumLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Auctor neque vitae tempus quam pellentesque. Scelerisque varius morbi enim nunc faucibus a. Tellus id interdum velit laoreet id donec ultrices. Aliquet bibendum enim facilisis gravida neque convallis a. Massa enim nec dui nunc mattis enim ut tellus. Sed felis eget velit aliquet sagittis id consectetur purus ut. Dignissim convallis aenean et tortor at risus. Integer vitae justo eget magna fermentum iaculis eu non. Ut placerat orci nulla pellentesque dignissim. Nisl suscipit adipiscing bibendum est ultricies. Mi ipsum faucibus vitae aliquet nec ullamcorper sit amet risus."),
),
),
],
),
);
}
}
Widget postActionBar(context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.reply),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.repeat),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.favorite_outline),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.more_horiz),
)
],
);
}

37
lib/partials/thread.dart Normal file
View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:slothmu/partials/post.dart';
class Thread extends StatefulWidget {
const Thread({Key? key}) : super(key: key);
@override
State<Thread> createState() => _ThreadState();
}
class _ThreadState extends State<Thread> {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(4),
child: Container(
padding: const EdgeInsets.all(24),
width: MediaQuery.of(context).size.width / 1.2,
constraints: const BoxConstraints(maxWidth: 1000, minWidth: 375),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border:
Border.all(color: Theme.of(context).colorScheme.secondary),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [Post(), Post(), Post()],
),
),
),
],
);
}
}

24
lib/themes/dracula.dart Normal file
View File

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'themes.dart' as themes;
themes.CustomColors theme = themes.CustomColors(
"Dracula",
const Color.fromARGB(
255,
98,
114,
164,
),
const ColorScheme(
brightness: Brightness.dark,
primary: Color.fromARGB(255, 255, 121, 198),
onPrimary: Color.fromARGB(255, 40, 42, 54),
secondary: Color.fromARGB(255, 80, 250, 123),
onSecondary: Color.fromARGB(255, 40, 42, 54),
error: Color.fromARGB(255, 255, 85, 85),
onError: Color.fromARGB(255, 40, 42, 54),
background: Color.fromARGB(255, 40, 42, 54),
onBackground: Color.fromARGB(255, 248, 248, 242),
surface: Color.fromARGB(255, 68, 71, 90),
onSurface: Color.fromARGB(255, 248, 248, 242),
));

View File

@ -1,15 +1,33 @@
import 'package:flutter/material.dart';
import 'dracula.dart' as color_dracula;
final defaultThemeDark = ThemeData(
colorScheme: const ColorScheme(
brightness: Brightness.dark,
primary: Colors.white,
onPrimary: Colors.black,
secondary: Colors.lime,
onSecondary: Colors.black,
error: Colors.red,
onError: Colors.white,
background: Colors.black,
onBackground: Colors.white,
surface: Colors.deepPurple,
onSurface: Colors.white));
final available = [color_dracula.theme];
ThemeData getTheme(CustomColors colors) {
return ThemeData(
scaffoldBackgroundColor: colors.colorScheme.background,
bottomAppBarColor: colors.colorScheme.background,
hintColor: colors.hintColor,
colorScheme: colors.colorScheme,
errorColor: colors.colorScheme.error,
bottomAppBarTheme: BottomAppBarTheme(
color: colors.colorScheme.surface,
shape: const CircularNotchedRectangle(),
elevation: 0,
),
navigationBarTheme: const NavigationBarThemeData(
backgroundColor: Colors.transparent,
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
indicatorColor: Colors.transparent,
elevation: 0,
height: 64,
),
scrollbarTheme: const ScrollbarThemeData(),
);
}
class CustomColors {
late String name;
late Color hintColor;
late ColorScheme colorScheme;
CustomColors(this.name, this.hintColor, this.colorScheme);
}

View File

@ -63,7 +63,7 @@ packages:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.1"
file:
dependency: transitive
description:
@ -210,13 +210,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
settings_ui:
dependency: "direct main"
description:
name: settings_ui
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
shared_preferences:
dependency: "direct main"
description:

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
version: 0.1.0+1
environment:
sdk: ">=2.17.3 <3.0.0"
@ -36,7 +36,6 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
settings_ui: ^2.0.2
http: ^0.13.4
localization: ^2.1.0
shared_preferences: ^2.0.15
@ -69,8 +68,6 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- lib/i18n/
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware