Merge branch 'main' into focus-mode

This commit is contained in:
Hank Grabowski 2024-07-26 10:34:00 -04:00
commit 9b79e19bac
30 changed files with 339 additions and 329 deletions

View file

@ -1,5 +1,4 @@
import 'package:color_blindness/color_blindness.dart'; import 'package:color_blindness/color_blindness.dart';
import 'package:color_blindness/color_blindness_color_scheme.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -27,7 +27,8 @@ class _VideoPlayerLibAvControlState extends State<VideoPlayerLibAvControl> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
videoPlayerController = VideoPlayerController.network(widget.videoUrl); videoPlayerController =
VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
} }
@override @override
@ -70,7 +71,7 @@ class _VideoPlayerLibAvControlState extends State<VideoPlayerLibAvControl> {
videoWidth = scaling * size.width; videoWidth = scaling * size.width;
videoHeight = scaling * size.height; videoHeight = scaling * size.height;
} }
print('Video Width: $videoWidth, Video Height: $videoHeight');
return GestureDetector( return GestureDetector(
onTap: toggleVideoPlay, onTap: toggleVideoPlay,
child: Column( child: Column(

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../models/connection.dart'; import '../../models/connection.dart';
@ -9,8 +8,6 @@ import '../../utils/active_profile_selector.dart';
import '../image_control.dart'; import '../image_control.dart';
class MentionAutocompleteOptions extends StatelessWidget { class MentionAutocompleteOptions extends StatelessWidget {
static final _logger = Logger('$MentionAutocompleteOptions');
const MentionAutocompleteOptions({ const MentionAutocompleteOptions({
super.key, super.key,
required this.id, required this.id,

View file

@ -38,11 +38,15 @@ class NotificationControl extends StatelessWidget {
} }
final loadedPost = await manager.refreshStatusChain(notification.iid); final loadedPost = await manager.refreshStatusChain(notification.iid);
if (loadedPost.isSuccess) { if (loadedPost.isSuccess) {
context.push('/post/view/${loadedPost.value.id}/${notification.iid}'); if (context.mounted) {
context.push('/post/view/${loadedPost.value.id}/${notification.iid}');
}
return; return;
} }
buildSnackbar( if (context.mounted) {
context, 'Error getting data for notification: ${loadedPost.error}'); buildSnackbar(
context, 'Error getting data for notification: ${loadedPost.error}');
}
} }
@override @override

View file

@ -5,7 +5,7 @@ import 'package:relatica/controls/padding.dart';
import 'package:relatica/globals.dart'; import 'package:relatica/globals.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../../models/TimelineIdentifiers.dart'; import '../../models/timeline_identifiers.dart';
import '../../services/network_status_service.dart'; import '../../services/network_status_service.dart';
import '../../services/setting_service.dart'; import '../../services/setting_service.dart';
import '../../services/timeline_manager.dart'; import '../../services/timeline_manager.dart';

View file

@ -8,7 +8,6 @@ import 'package:result_monad/result_monad.dart';
import '../friendica_client/paged_response.dart'; import '../friendica_client/paged_response.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/TimelineIdentifiers.dart';
import '../models/auth/profile.dart'; import '../models/auth/profile.dart';
import '../models/circle_data.dart'; import '../models/circle_data.dart';
import '../models/connection.dart'; import '../models/connection.dart';
@ -22,6 +21,7 @@ import '../models/media_attachment_uploads/image_types_enum.dart';
import '../models/search_results.dart'; import '../models/search_results.dart';
import '../models/search_types.dart'; import '../models/search_types.dart';
import '../models/timeline_entry.dart'; import '../models/timeline_entry.dart';
import '../models/timeline_identifiers.dart';
import '../models/user_notification.dart'; import '../models/user_notification.dart';
import '../models/visibility.dart'; import '../models/visibility.dart';
import '../serializers/friendica/direct_message_friendica_extensions.dart'; import '../serializers/friendica/direct_message_friendica_extensions.dart';

View file

@ -39,7 +39,6 @@ void main() async {
getIt.registerSingleton<LogService>(logService); getIt.registerSingleton<LogService>(logService);
// await dotenv.load(fileName: '.env'); // await dotenv.load(fileName: '.env');
const enablePreview = false;
Logger.root.level = Level.OFF; Logger.root.level = Level.OFF;
Logger.root.onRecord.listen((event) { Logger.root.onRecord.listen((event) {
logService.add(event); logService.add(event);
@ -105,7 +104,6 @@ class _AppState extends fr.ConsumerState<App> {
return AnimatedBuilder( return AnimatedBuilder(
builder: (context, child) { builder: (context, child) {
Logger.root.level = settingsService.logLevel; Logger.root.level = settingsService.logLevel;
print('Log level: ${settingsService.logLevel}');
return Portal( return Portal(
child: MultiProvider( child: MultiProvider(
providers: [ providers: [

View file

@ -1,6 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:result_monad/src/result_monad_base.dart'; import 'package:result_monad/result_monad.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../exec_error.dart'; import '../exec_error.dart';

View file

@ -1,5 +1,5 @@
import 'TimelineIdentifiers.dart';
import 'entry_tree_item.dart'; import 'entry_tree_item.dart';
import 'timeline_identifiers.dart';
const defaultLowestId = 9223372036854775807; const defaultLowestId = 9223372036854775807;
const defaultHighestId = 0; const defaultHighestId = 0;

View file

@ -54,7 +54,9 @@ class _CircleAddUsersScreenState extends State<CircleAddUsersScreen> {
onSuccess: (_) => 'Added $messageBase', onSuccess: (_) => 'Added $messageBase',
onError: (error) => 'Error adding $messageBase: $error', onError: (error) => 'Error adding $messageBase: $error',
); );
buildSnackbar(context, message); if (mounted) {
buildSnackbar(context, message);
}
} }
} }

View file

@ -81,7 +81,9 @@ class _CircleEditorScreenState extends State<CircleEditorScreen> {
onSuccess: (_) => 'Removed $messageBase', onSuccess: (_) => 'Removed $messageBase',
onError: (error) => 'Error removing $messageBase: $error', onError: (error) => 'Error removing $messageBase: $error',
); );
buildSnackbar(context, message); if (mounted) {
buildSnackbar(context, message);
}
} }
} }

View file

@ -178,7 +178,7 @@ class _EditorScreenState extends State<EditorScreen> {
return; return;
} }
if (mounted && context.canPop()) { if (context.mounted && context.canPop()) {
context.pop(); context.pop();
} }
} }
@ -220,7 +220,7 @@ class _EditorScreenState extends State<EditorScreen> {
return; return;
} }
if (mounted && context.canPop()) { if (context.mounted && context.canPop()) {
context.pop(); context.pop();
} }
} }
@ -254,51 +254,49 @@ class _EditorScreenState extends State<EditorScreen> {
} else { } else {
final mainBody = Padding( final mainBody = Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Container( child: SingleChildScrollView(
child: SingleChildScrollView( child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, children: [
children: [ if (isComment && parentEntry != null)
if (isComment && parentEntry != null) buildCommentPreview(context, parentEntry!),
buildCommentPreview(context, parentEntry!), TextFormField(
TextFormField( readOnly: isSubmitting,
readOnly: isSubmitting, enabled: !isSubmitting && canSpoilerText,
enabled: !isSubmitting && canSpoilerText, controller: spoilerController,
controller: spoilerController, textCapitalization: TextCapitalization.sentences,
textCapitalization: TextCapitalization.sentences, decoration: InputDecoration(
decoration: InputDecoration( labelText: canSpoilerText
labelText: canSpoilerText ? '$statusType Spoiler Text (optional)'
? '$statusType Spoiler Text (optional)' : 'Your server doesnt support $statusType Spoiler Text',
: 'Your server doesnt support $statusType Spoiler Text', border: OutlineInputBorder(
border: OutlineInputBorder( borderSide: const BorderSide(),
borderSide: const BorderSide(), borderRadius: BorderRadius.circular(5.0),
borderRadius: BorderRadius.circular(5.0),
),
), ),
), ),
const VerticalPadding(), ),
buildVisibilitySelector(context), const VerticalPadding(),
const VerticalPadding(), buildVisibilitySelector(context),
buildContentField(context), const VerticalPadding(),
CharacterCountWidget( buildContentField(context),
contentController: contentController, CharacterCountWidget(
linkPreviewController: linkPreviewController, contentController: contentController,
), linkPreviewController: linkPreviewController,
const VerticalPadding(), ),
buildLinkWithPreview(context), const VerticalPadding(),
const VerticalPadding(), buildLinkWithPreview(context),
GallerySelectorControl( const VerticalPadding(),
entries: existingMediaItems, GallerySelectorControl(
visibilityFilter: visibility, entries: existingMediaItems,
), visibilityFilter: visibility,
const VerticalPadding(), ),
MediaUploadsControl( const VerticalPadding(),
entryMediaItems: newMediaItems, MediaUploadsControl(
), entryMediaItems: newMediaItems,
buildButtonBar(context, manager), ),
], buildButtonBar(context, manager),
), ],
), ),
), ),
); );

View file

@ -133,10 +133,12 @@ class _FilterEditorScreenState extends State<FilterEditorScreen> {
} }
if (filteredAuthors.contains(newConnection)) { if (filteredAuthors.contains(newConnection)) {
buildSnackbar( if (context.mounted) {
context, buildSnackbar(
'Already filtering on ${newConnection.handle}', context,
); 'Already filtering on ${newConnection.handle}',
);
}
} }
setState(() { setState(() {
filteredAuthors.add(newConnection); filteredAuthors.add(newConnection);

View file

@ -13,7 +13,7 @@ import '../controls/responsive_max_width.dart';
import '../controls/standard_app_drawer.dart'; import '../controls/standard_app_drawer.dart';
import '../controls/timeline/timeline_panel.dart'; import '../controls/timeline/timeline_panel.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/TimelineIdentifiers.dart'; import '../models/timeline_identifiers.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import '../services/network_status_service.dart'; import '../services/network_status_service.dart';
import '../services/timeline_manager.dart'; import '../services/timeline_manager.dart';

View file

@ -106,7 +106,7 @@ class _ImageEditorScreenState extends State<ImageEditorScreen> {
context, context,
'Cancel changes?', 'Cancel changes?',
); );
if (ok == true && mounted) { if (ok == true && context.mounted) {
context.pop(); context.pop();
} }
}, },

View file

@ -67,7 +67,7 @@ class _MediaViewerScreenState extends State<MediaViewerScreen> {
final bytesResult = final bytesResult =
await RemoteFileClient(getIt<AccountsService>().currentProfile) await RemoteFileClient(getIt<AccountsService>().currentProfile)
.getFileBytes(attachment.uri); .getFileBytes(attachment.uri);
if (bytesResult.isFailure && mounted) { if (bytesResult.isFailure && context.mounted) {
buildSnackbar(context, buildSnackbar(context,
'Error getting full size version of file: ${bytesResult.error}'); 'Error getting full size version of file: ${bytesResult.error}');
return; return;
@ -75,10 +75,13 @@ class _MediaViewerScreenState extends State<MediaViewerScreen> {
if (Platform.isAndroid || Platform.isIOS) { if (Platform.isAndroid || Platform.isIOS) {
final saveResult = await ImageGallerySaver.saveImage(bytesResult.value); final saveResult = await ImageGallerySaver.saveImage(bytesResult.value);
if (saveResult['isSuccess']) { if (context.mounted) {
buildSnackbar(context, 'Image saved to gallery'); if (saveResult['isSuccess']) {
} else { buildSnackbar(context, 'Image saved to gallery');
buildSnackbar(context, 'Unable to save to gallery, check permissions'); } else {
buildSnackbar(
context, 'Unable to save to gallery, check permissions');
}
} }
} else { } else {
final location = await FilePicker.platform.saveFile( final location = await FilePicker.platform.saveFile(

View file

@ -168,7 +168,9 @@ class NotificationsScreen extends StatelessWidget {
final message = (await manager.markAllAsRead()).fold( final message = (await manager.markAllAsRead()).fold(
onSuccess: (_) => 'Marked all notifications as read', onSuccess: (_) => 'Marked all notifications as read',
onError: (error) => 'Error marking notifications: $error'); onError: (error) => 'Error marking notifications: $error');
buildSnackbar(context, message); if (context.mounted) {
buildSnackbar(context, message);
}
} }
} }
} }

View file

@ -62,13 +62,11 @@ class _SearchScreenState extends State<SearchScreen> {
} }
Future<void> updateSearchResults(Profile profile, {bool reset = true}) async { Future<void> updateSearchResults(Profile profile, {bool reset = true}) async {
print('Starting update');
if (reset) { if (reset) {
nextPage = PagingData(limit: limit); nextPage = PagingData(limit: limit);
searchResult = SearchResults.empty(); searchResult = SearchResults.empty();
} }
print('Search $searchType on ${searchTextController.text}');
final result = await SearchClient(profile) final result = await SearchClient(profile)
.search(searchType, searchTextController.text, nextPage); .search(searchType, searchTextController.text, nextPage);
result.match( result.match(
@ -80,12 +78,9 @@ class _SearchScreenState extends State<SearchScreen> {
onError: (error) => onError: (error) =>
buildSnackbar(context, 'Error getting search result: $error'), buildSnackbar(context, 'Error getting search result: $error'),
); );
print('Ending update');
} }
clearSearchResults() { clearSearchResults() {
print('Clearing results');
setState(() { setState(() {
searchResult = SearchResults.empty(); searchResult = SearchResults.empty();
searchTextController.text = ''; searchTextController.text = '';
@ -329,7 +324,7 @@ class _SearchScreenState extends State<SearchScreen> {
await cm.fullRefresh(connection); await cm.fullRefresh(connection);
} }
}); });
if (context.mounted) { if (mounted) {
context.pushNamed(ScreenPaths.userProfile, context.pushNamed(ScreenPaths.userProfile,
pathParameters: {'id': connection.id}); pathParameters: {'id': connection.id});
} }

View file

@ -219,7 +219,7 @@ class NetworkCapabilitiesWidget extends StatelessWidget {
} }
return ListTile( return ListTile(
title: Row( title: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Network Capabilities'), Text('Network Capabilities'),
@ -230,7 +230,7 @@ class NetworkCapabilitiesWidget extends StatelessWidget {
], ],
), ),
subtitle: DataTable( subtitle: DataTable(
columns: [ columns: const [
DataColumn(label: Text('Network')), DataColumn(label: Text('Network')),
DataColumn(label: Text('React')), DataColumn(label: Text('React')),
DataColumn(label: Text('Reshare')), DataColumn(label: Text('Reshare')),

View file

@ -61,8 +61,7 @@ class _SignInScreenState extends State<SignInScreen> {
void _updateSignInButtonStatus() { void _updateSignInButtonStatus() {
setState(() { setState(() {
signInButtonEnabled = switch (oauthType) { signInButtonEnabled = switch (oauthType) {
usernamePasswordType => usernamePasswordType => serverNameController.text.isNotEmpty &&
serverNameController.text.isNotEmpty &&
usernameController.text.isNotEmpty && usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty, passwordController.text.isNotEmpty,
oauthType => serverNameController.text.isNotEmpty, oauthType => serverNameController.text.isNotEmpty,
@ -141,249 +140,258 @@ class _SignInScreenState extends State<SignInScreen> {
child: Form( child: Form(
key: formKey, key: formKey,
child: Center( child: Center(
child: ListView( child: AutofillGroup(
children: [ child: ListView(
Center( children: [
child: DropdownButton<String>( Center(
value: authType, child: DropdownButton<String>(
items: authTypes value: authType,
.map( items: authTypes
(a) => DropdownMenuItem(value: a, child: Text(a))) .map((a) =>
.toList(), DropdownMenuItem(value: a, child: Text(a)))
onChanged: existingAccount .toList(),
? null onChanged: existingAccount
: (value) { ? null
if (existingAccount) { : (value) {
buildSnackbar(context, if (existingAccount) {
"Can't change the type on an existing account"); buildSnackbar(context,
return; "Can't change the type on an existing account");
} return;
authType = value!; }
setState(() {}); authType = value!;
}), setState(() {});
), }),
const VerticalPadding(),
TextFormField(
autocorrect: false,
readOnly: existingAccount,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: serverNameController,
validator: (value) =>
isFQDN(value ?? '') ? null : 'Not a valid server name',
decoration: InputDecoration(
hintText: 'Server Name (friendica.example.com)',
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme
.of(context)
.colorScheme
.surface,
),
borderRadius: BorderRadius.circular(5.0),
),
labelText: 'Server Name',
),
),
const VerticalPadding(),
if (!showUsernameAndPasswordFields) ...[
Text(
existingAccount
? 'Configured to sign in as user ${existingProfile
?.handle}'
: 'Relatica will open the requested Friendica site in a web browser where you will be asked to authorize this client.',
softWrap: true,
), ),
const VerticalPadding(), const VerticalPadding(),
],
if (showUsernameAndPasswordFields) ...[
TextFormField( TextFormField(
autocorrect: false,
readOnly: existingAccount, readOnly: existingAccount,
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
controller: usernameController, controller: serverNameController,
keyboardType: TextInputType.emailAddress, validator: (value) =>
validator: (value) { isFQDN(value ?? '') ? null : 'Not a valid server name',
if (value == null) { decoration: InputDecoration(
return null; hintText: 'Server Name (friendica.example.com)',
} border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.surface,
),
borderRadius: BorderRadius.circular(5.0),
),
labelText: 'Server Name',
),
),
const VerticalPadding(),
if (!showUsernameAndPasswordFields) ...[
Text(
existingAccount
? 'Configured to sign in as user ${existingProfile?.handle}'
: 'Relatica will open the requested Friendica site in a web browser where you will be asked to authorize this client.',
softWrap: true,
),
const VerticalPadding(),
],
if (showUsernameAndPasswordFields) ...[
TextFormField(
readOnly: existingAccount,
autovalidateMode: AutovalidateMode.onUserInteraction,
autofillHints: const [AutofillHints.username],
controller: usernameController,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null) {
return null;
}
if (value.contains('@')) { if (value.contains('@')) {
return isEmail(value) final properFormat = isEmail(value);
if (!properFormat) {
return 'Not a valid Friendica Account Address';
}
final elements = value.split('@');
if (elements.last != serverNameController.text) {
return 'Server name must match above field.\nUsername should be the *Friendica* username.\nThis is not the email address of the account.';
}
return null;
}
return isAlphanumeric(value.replaceAll('-', ''))
? null ? null
: 'Not a valid Friendica Account Address'; : 'Username should be alpha-numeric';
} },
decoration: InputDecoration(
return isAlphanumeric(value.replaceAll('-', '')) prefixIcon: const Icon(Icons.alternate_email),
? null hintText:
: 'Username should be alpha-numeric'; 'Your username on the server (not email address)',
}, border: OutlineInputBorder(
decoration: InputDecoration( borderSide: BorderSide(
prefixIcon: const Icon(Icons.alternate_email), color: Theme.of(context).colorScheme.surface,
hintText: 'Username (user@example.com)', ),
border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0),
borderSide: BorderSide(
color: Theme
.of(context)
.colorScheme
.surface,
), ),
borderRadius: BorderRadius.circular(5.0), labelText: 'Username',
), ),
labelText: 'Username',
), ),
), const VerticalPadding(),
const VerticalPadding(), TextFormField(
TextFormField( readOnly: existingAccount,
readOnly: existingAccount, obscureText: hidePassword,
obscureText: hidePassword, controller: passwordController,
controller: passwordController, autofillHints: const [AutofillHints.password],
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Password field cannot be empty'; return 'Password field cannot be empty';
} }
return null; return null;
}, },
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.password), prefixIcon: const Icon(Icons.password),
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
hidePassword = !hidePassword; hidePassword = !hidePassword;
}); });
}, },
icon: hidePassword icon: hidePassword
? const Icon(Icons.remove_red_eye_outlined) ? const Icon(Icons.remove_red_eye_outlined)
: const Icon(Icons.remove_red_eye), : const Icon(Icons.remove_red_eye),
),
hintText: 'Password',
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme
.of(context)
.colorScheme
.surface,
), ),
borderRadius: BorderRadius.circular(5.0), hintText: 'Password',
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.surface,
),
borderRadius: BorderRadius.circular(5.0),
),
labelText: 'Password',
), ),
labelText: 'Password',
), ),
), const VerticalPadding(),
],
signInButtonEnabled
? ElevatedButton(
onPressed: () async => await _signIn(context),
child: const Text('Signin'),
)
: const SizedBox(),
const VerticalPadding(), const VerticalPadding(),
Text(
'Logged out:',
style: Theme.of(context).textTheme.headlineSmall,
),
loggedOutProfiles.isEmpty
? const Text(
'No logged out profiles',
textAlign: TextAlign.center,
)
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 0.5,
)),
child: Column(
children: loggedOutProfiles.map((p) {
return ListTile(
onTap: () {
setCredentials(context, p);
setState(() {});
},
title: Text(p.handle),
subtitle: Text(p.credentials is BasicCredentials
? usernamePasswordType
: oauthType),
trailing: ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(
context,
'Remove login information from app?');
if (confirm ?? false) {
await service.removeProfile(p);
}
setState(() {});
},
child: const Text('Remove'),
),
);
}).toList(),
),
),
const VerticalPadding(),
Text(
'Logged in:',
style: Theme.of(context).textTheme.headlineSmall,
),
loggedInProfiles.isEmpty
? const Text(
'No logged in profiles',
textAlign: TextAlign.center,
)
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 0.5,
)),
child: Column(
children: loggedInProfiles.map((p) {
final active = service.loggedIn
? p.id == service.currentProfile.id
: false;
return ListTile(
onTap: () async {
setCredentials(context, p);
setState(() {});
},
title: Text(
p.handle,
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
),
subtitle: Text(
p.credentials is BasicCredentials
? usernamePasswordType
: oauthType,
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
),
trailing: ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(
context, 'Log out account?');
if (confirm == true) {
await getIt<AccountsService>().signOut(p);
setState(() {});
}
},
child: const Text('Sign out'),
),
);
}).toList(),
),
),
const VerticalPadding(),
ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(context,
'Are you sure you want to logout and delete *all* accounts? This cannot be undone.') ??
false;
if (!confirm) {
return;
}
await getIt<AccountsService>().clearAllProfiles();
},
child: const Text('Clear All')),
], ],
signInButtonEnabled ),
? ElevatedButton(
onPressed: () async => await _signIn(context),
child: const Text('Signin'),
)
: const SizedBox(),
const VerticalPadding(),
Text(
'Logged out:',
style: Theme
.of(context)
.textTheme
.headlineSmall,
),
loggedOutProfiles.isEmpty
? const Text(
'No logged out profiles',
textAlign: TextAlign.center,
)
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 0.5,
)),
child: Column(
children: loggedOutProfiles.map((p) {
return ListTile(
onTap: () {
setCredentials(context, p);
setState(() {});
},
title: Text(p.handle),
subtitle: Text(p.credentials is BasicCredentials
? usernamePasswordType
: oauthType),
trailing: ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(context,
'Remove login information from app?');
if (confirm ?? false) {
await service.removeProfile(p);
}
setState(() {});
},
child: const Text('Remove'),
),
);
}).toList(),
),
),
const VerticalPadding(),
Text(
'Logged in:',
style: Theme
.of(context)
.textTheme
.headlineSmall,
),
loggedInProfiles.isEmpty
? const Text(
'No logged in profiles',
textAlign: TextAlign.center,
)
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 0.5,
)),
child: Column(
children: loggedInProfiles.map((p) {
final active = service.loggedIn
? p.id == service.currentProfile.id
: false;
return ListTile(
onTap: () async {
setCredentials(context, p);
setState(() {});
},
title: Text(
p.handle,
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
),
subtitle: Text(
p.credentials is BasicCredentials
? usernamePasswordType
: oauthType,
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
),
trailing: ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(
context, 'Log out account?');
if (confirm == true) {
await getIt<AccountsService>().signOut(p);
setState(() {});
}
},
child: const Text('Sign out'),
),
);
}).toList(),
),
),
],
), ),
), ),
), ),
@ -405,8 +413,9 @@ class _SignInScreenState extends State<SignInScreen> {
} else { } else {
switch (authType) { switch (authType) {
case usernamePasswordType: case usernamePasswordType:
final username = usernameController.text.split('@').first;
creds = BasicCredentials( creds = BasicCredentials(
username: usernameController.text, username: username,
password: passwordController.text, password: passwordController.text,
serverName: serverNameController.text); serverName: serverNameController.text);
break; break;
@ -428,16 +437,16 @@ class _SignInScreenState extends State<SignInScreen> {
creds, creds,
withNotification: false, withNotification: false,
); );
if (mounted && result.isFailure) { if (context.mounted && result.isFailure) {
buildSnackbar(context, 'Error signing in: ${result.error}'); buildSnackbar(context, 'Error signing in: ${result.error}');
return; return;
} }
if (mounted) { if (context.mounted) {
buildSnackbar(context, 'Account signed in...'); buildSnackbar(context, 'Account signed in...');
} }
await getIt<AccountsService>().setActiveProfile(result.value); await getIt<AccountsService>().setActiveProfile(result.value);
if (mounted) { if (context.mounted) {
context.goNamed(ScreenPaths.timelines); context.goNamed(ScreenPaths.timelines);
} }
} }

View file

@ -4,7 +4,7 @@ import '../controls/linear_status_indicator.dart';
import '../controls/standard_appbar.dart'; import '../controls/standard_appbar.dart';
import '../controls/timeline/timeline_panel.dart'; import '../controls/timeline/timeline_panel.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/TimelineIdentifiers.dart'; import '../models/timeline_identifiers.dart';
import '../services/network_status_service.dart'; import '../services/network_status_service.dart';
import '../services/timeline_manager.dart'; import '../services/timeline_manager.dart';
import '../utils/active_profile_selector.dart'; import '../utils/active_profile_selector.dart';

View file

@ -189,7 +189,7 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
} else { } else {
await manager.removeUserFromCircle(g, profile); await manager.removeUserFromCircle(g, profile);
} }
if (mounted) { if (context.mounted) {
buildSnackbar(context, "User's Circles Updated"); buildSnackbar(context, "User's Circles Updated");
} }

View file

@ -322,7 +322,7 @@ class ConnectionsManager extends ChangeNotifier {
Result<List<CircleData>, ExecError> getCirclesForUser(String id) { Result<List<CircleData>, ExecError> getCirclesForUser(String id) {
final result = circlesRepo.getCirclesForUser(id); final result = circlesRepo.getCirclesForUser(id);
if (result.isSuccess) { if (result.isSuccess) {
print("Circles for user $id: $result"); _logger.finer("Circles for user $id: $result");
return result; return result;
} }

View file

@ -6,13 +6,13 @@ import 'package:result_monad/result_monad.dart';
import '../friendica_client/friendica_client.dart'; import '../friendica_client/friendica_client.dart';
import '../friendica_client/paging_data.dart'; import '../friendica_client/paging_data.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/TimelineIdentifiers.dart';
import '../models/auth/profile.dart'; import '../models/auth/profile.dart';
import '../models/entry_tree_item.dart'; import '../models/entry_tree_item.dart';
import '../models/exec_error.dart'; import '../models/exec_error.dart';
import '../models/image_entry.dart'; import '../models/image_entry.dart';
import '../models/media_attachment_uploads/new_entry_media_items.dart'; import '../models/media_attachment_uploads/new_entry_media_items.dart';
import '../models/timeline_entry.dart'; import '../models/timeline_entry.dart';
import '../models/timeline_identifiers.dart';
import '../models/visibility.dart'; import '../models/visibility.dart';
import '../utils/active_profile_selector.dart'; import '../utils/active_profile_selector.dart';
import 'media_upload_attachment_helper.dart'; import 'media_upload_attachment_helper.dart';

View file

@ -80,8 +80,9 @@ class NotificationsManager extends ChangeNotifier {
first = false; first = false;
result.match( result.match(
onSuccess: (nd) => print('Got ${nd.data.length} notifications'), onSuccess: (nd) =>
onError: (e) => debugPrint('Error getting notification: $e')); _logger.fine('Got ${nd.data.length} notifications'),
onError: (e) => _logger.severe('Error getting notification: $e'));
final response = result.getValueOrElse(() => PagedResponse([])); final response = result.getValueOrElse(() => PagedResponse([]));
response.data response.data
.where((n) => .where((n) =>

View file

@ -4,7 +4,6 @@ import 'package:result_monad/result_monad.dart';
import '../data/interfaces/circles_repo_intf.dart'; import '../data/interfaces/circles_repo_intf.dart';
import '../friendica_client/friendica_client.dart'; import '../friendica_client/friendica_client.dart';
import '../models/TimelineIdentifiers.dart';
import '../models/auth/profile.dart'; import '../models/auth/profile.dart';
import '../models/circle_data.dart'; import '../models/circle_data.dart';
import '../models/entry_tree_item.dart'; import '../models/entry_tree_item.dart';
@ -13,6 +12,7 @@ import '../models/image_entry.dart';
import '../models/media_attachment_uploads/new_entry_media_items.dart'; import '../models/media_attachment_uploads/new_entry_media_items.dart';
import '../models/timeline.dart'; import '../models/timeline.dart';
import '../models/timeline_entry.dart'; import '../models/timeline_entry.dart';
import '../models/timeline_identifiers.dart';
import '../models/visibility.dart'; import '../models/visibility.dart';
import 'entry_manager_service.dart'; import 'entry_manager_service.dart';

View file

@ -2,10 +2,10 @@ import '../models/entry_tree_item.dart';
import '../models/flattened_tree_item.dart'; import '../models/flattened_tree_item.dart';
extension FlatteningExtensions on EntryTreeItem { extension FlatteningExtensions on EntryTreeItem {
static const BaseLevel = 0; static const baseLevel = 0;
List<FlattenedTreeItem> flatten( List<FlattenedTreeItem> flatten(
{int level = BaseLevel, bool topLevelOnly = false}) { {int level = baseLevel, bool topLevelOnly = false}) {
final items = <FlattenedTreeItem>[]; final items = <FlattenedTreeItem>[];
final myEntry = FlattenedTreeItem( final myEntry = FlattenedTreeItem(
timelineEntry: entry, timelineEntry: entry,
@ -23,7 +23,7 @@ extension FlatteningExtensions on EntryTreeItem {
c1.entry.creationTimestamp.compareTo(c2.entry.creationTimestamp)); c1.entry.creationTimestamp.compareTo(c2.entry.creationTimestamp));
for (final child in sortedChildren) { for (final child in sortedChildren) {
int childLevel = level + 1; int childLevel = level + 1;
if (child.entry.authorId == entry.authorId && level != BaseLevel) { if (child.entry.authorId == entry.authorId && level != baseLevel) {
childLevel = level; childLevel = level;
} }

View file

@ -38,9 +38,7 @@ void _updateSwapTagLinks(Node node, List<String> tags) {
if (hasExpectedTag) { if (hasExpectedTag) {
final profile = getIt<AccountsService>().currentProfile; final profile = getIt<AccountsService>().currentProfile;
final newTagUrl = generateTagUrlFromProfile(profile, tag); final newTagUrl = generateTagUrlFromProfile(profile, tag);
print(node.attributes['href']);
node.attributes['href'] = newTagUrl.toString(); node.attributes['href'] = newTagUrl.toString();
print(node.attributes['href']);
} }
} }
for (var n in node.nodes) { for (var n in node.nodes) {

View file

@ -1,6 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart' show MapEquality;
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart'; import 'package:result_monad/result_monad.dart';
@ -149,7 +149,6 @@ class _ExpiringRequestCache {
response, response,
); );
print('Adding cached response for $type => $url');
_responses[cacheEntry] = cacheEntry; _responses[cacheEntry] = cacheEntry;
} }