Add settings manager and move some things around
This commit is contained in:
parent
bc57b20219
commit
9ebe4fc63d
26 changed files with 383 additions and 121 deletions
BIN
assets/images/logo.png
Normal file
BIN
assets/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
BIN
assets/images/logo512.png
Normal file
BIN
assets/images/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -1,7 +1,7 @@
|
|||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/models/friend.dart';
|
||||
import 'package:contacts_plus_plus/models/user.dart';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/models/message.dart';
|
||||
|
||||
class MessageApi {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/models/user.dart';
|
||||
|
||||
class UserApi {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:contacts_plus_plus/neos_hub.dart';
|
||||
import 'package:contacts_plus_plus/clients/neos_hub.dart';
|
||||
import 'package:contacts_plus_plus/clients/settings_client.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||
|
@ -9,7 +10,7 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:contacts_plus_plus/models/authentication_data.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'config.dart';
|
||||
import '../config.dart';
|
||||
|
||||
class ApiClient {
|
||||
static const String userIdKey = "userId";
|
||||
|
@ -144,15 +145,19 @@ class ApiClient {
|
|||
}
|
||||
|
||||
class ClientHolder extends InheritedWidget {
|
||||
final ApiClient client;
|
||||
final ApiClient apiClient;
|
||||
final SettingsClient settingsClient;
|
||||
late final NeosHub hub;
|
||||
|
||||
ClientHolder({super.key, required AuthenticationData authenticationData, required super.child})
|
||||
: client = ApiClient(authenticationData: authenticationData) {
|
||||
hub = NeosHub(apiClient: client);
|
||||
ClientHolder({
|
||||
super.key,
|
||||
required AuthenticationData authenticationData,
|
||||
required this.settingsClient,
|
||||
required super.child
|
||||
}) : apiClient = ApiClient(authenticationData: authenticationData) {
|
||||
hub = NeosHub(apiClient: apiClient);
|
||||
}
|
||||
|
||||
|
||||
static ClientHolder? maybeOf(BuildContext context) {
|
||||
return context.dependOnInheritedWidgetOfExactType<ClientHolder>();
|
||||
}
|
||||
|
@ -164,5 +169,8 @@ class ClientHolder extends InheritedWidget {
|
|||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(covariant ClientHolder oldWidget) => oldWidget.client != client;
|
||||
bool updateShouldNotify(covariant ClientHolder oldWidget) =>
|
||||
oldWidget.apiClient != apiClient
|
||||
|| oldWidget.settingsClient != settingsClient
|
||||
|| oldWidget.hub != hub;
|
||||
}
|
|
@ -3,7 +3,7 @@ import 'dart:io';
|
|||
import 'package:contacts_plus_plus/apis/message_api.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/config.dart';
|
||||
import 'package:contacts_plus_plus/models/message.dart';
|
||||
import 'package:logging/logging.dart';
|
28
lib/clients/settings_client.dart
Normal file
28
lib/clients/settings_client.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:contacts_plus_plus/models/settings.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
|
||||
class SettingsClient {
|
||||
static const String _settingsKey = "settings";
|
||||
static const _storage = FlutterSecureStorage();
|
||||
Settings _currentSettings = Settings.def();
|
||||
|
||||
Settings get currentSettings => _currentSettings;
|
||||
|
||||
Future<void> loadSettings() async {
|
||||
final data = await _storage.read(key: _settingsKey);
|
||||
if (data == null) return;
|
||||
try {
|
||||
_currentSettings = Settings.fromMap(jsonDecode(data));
|
||||
} catch (_) {
|
||||
_storage.delete(key: _settingsKey);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeSettings(Settings newSettings) async {
|
||||
_currentSettings = newSettings;
|
||||
await _storage.write(key: _settingsKey, value: jsonEncode(newSettings.toMap()));
|
||||
}
|
||||
}
|
|
@ -1,20 +1,28 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:contacts_plus_plus/clients/settings_client.dart';
|
||||
import 'package:contacts_plus_plus/widgets/friends_list.dart';
|
||||
import 'package:contacts_plus_plus/widgets/login_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'api_client.dart';
|
||||
import 'clients/api_client.dart';
|
||||
import 'models/authentication_data.dart';
|
||||
|
||||
void main() {
|
||||
void main() async {
|
||||
Logger.root.onRecord.listen((event) => log(event.message, name: event.loggerName));
|
||||
runApp(Phoenix(child: const ContactsPlusPlus()));
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final settingsClient = SettingsClient();
|
||||
await settingsClient.loadSettings();
|
||||
runApp(Phoenix(child: ContactsPlusPlus(settingsClient: settingsClient,)));
|
||||
}
|
||||
|
||||
class ContactsPlusPlus extends StatefulWidget {
|
||||
const ContactsPlusPlus({super.key});
|
||||
const ContactsPlusPlus({required this.settingsClient, super.key});
|
||||
|
||||
final SettingsClient settingsClient;
|
||||
|
||||
@override
|
||||
State<ContactsPlusPlus> createState() => _ContactsPlusPlusState();
|
||||
|
@ -27,6 +35,7 @@ class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClientHolder(
|
||||
settingsClient: widget.settingsClient,
|
||||
authenticationData: _authData,
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
|
@ -40,6 +49,41 @@ class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
|
|||
const FriendsList() :
|
||||
LoginScreen(
|
||||
onLoginSuccessful: (AuthenticationData authData) async {
|
||||
final notificationManager = FlutterLocalNotificationsPlugin();
|
||||
final settings = widget.settingsClient.currentSettings;
|
||||
final platformNotifications = notificationManager.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
||||
if (!settings.notificationsDenied && !(await platformNotifications?.areNotificationsEnabled() ?? true)) {
|
||||
if (context.mounted) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text("This app needs to ask your permission to send background notifications."),
|
||||
content: Text("Are you okay with that?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await widget.settingsClient.changeSettings(settings.copyWith(notificationsDenied: true));
|
||||
},
|
||||
child: const Text("No"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await widget.settingsClient.changeSettings(settings.copyWith(notificationsDenied: false));
|
||||
await notificationManager.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermission();
|
||||
},
|
||||
child: const Text("Yes"),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
if (authData.isAuthenticated) {
|
||||
setState(() {
|
||||
_authData = authData;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/apis/message_api.dart';
|
||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
|
|
@ -1,5 +1,33 @@
|
|||
|
||||
class Settings {
|
||||
// No settings right now.
|
||||
static const Settings _defaultSettings = Settings(notificationsDenied: true, unreadCheckIntervalMinutes: 0);
|
||||
final bool notificationsDenied;
|
||||
final int unreadCheckIntervalMinutes;
|
||||
|
||||
const Settings({required this.notificationsDenied, required this.unreadCheckIntervalMinutes});
|
||||
|
||||
factory Settings.def() => _defaultSettings;
|
||||
|
||||
factory Settings.fromMap(Map map) {
|
||||
return Settings(
|
||||
notificationsDenied: map["notificationsDenied"] ?? _defaultSettings.notificationsDenied,
|
||||
unreadCheckIntervalMinutes: map["unreadCheckIntervalMinutes"] ?? _defaultSettings.unreadCheckIntervalMinutes,
|
||||
);
|
||||
}
|
||||
|
||||
Map toMap() {
|
||||
return {
|
||||
"notificationsDenied": notificationsDenied,
|
||||
"unreadCheckIntervalMinutes": unreadCheckIntervalMinutes,
|
||||
};
|
||||
}
|
||||
|
||||
Settings copy() => copyWith();
|
||||
|
||||
Settings copyWith({bool? notificationsDenied, int? unreadCheckIntervalMinutes}) {
|
||||
return Settings(
|
||||
notificationsDenied: notificationsDenied ?? this.notificationsDenied,
|
||||
unreadCheckIntervalMinutes: unreadCheckIntervalMinutes ?? this.unreadCheckIntervalMinutes,
|
||||
);
|
||||
}
|
||||
}
|
41
lib/widgets/default_error_widget.dart
Normal file
41
lib/widgets/default_error_widget.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class DefaultErrorWidget extends StatelessWidget {
|
||||
const DefaultErrorWidget({required this.message, this.onRetry, super.key});
|
||||
|
||||
final String message;
|
||||
final void Function()? onRetry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 64, vertical: 128,),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text("Something went wrong: ", style: Theme
|
||||
.of(context)
|
||||
.textTheme
|
||||
.titleMedium,),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(message),
|
||||
),
|
||||
if (onRetry != null) TextButton.icon(
|
||||
onPressed: onRetry,
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text("Retry"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||
import 'package:contacts_plus_plus/models/friend.dart';
|
||||
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
||||
import 'package:contacts_plus_plus/widgets/messages.dart';
|
||||
import 'package:contacts_plus_plus/widgets/messages_list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FriendListTile extends StatelessWidget {
|
||||
|
@ -23,7 +23,7 @@ class FriendListTile extends StatelessWidget {
|
|||
title: Text(friend.username),
|
||||
subtitle: Text(friend.userStatus.onlineStatus.name),
|
||||
onTap: () async {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Messages(friend: friend)));
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessagesList(friend: friend)));
|
||||
await onTap?.call();
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/apis/friend_api.dart';
|
||||
import 'package:contacts_plus_plus/apis/message_api.dart';
|
||||
import 'package:contacts_plus_plus/models/friend.dart';
|
||||
import 'package:contacts_plus_plus/models/message.dart';
|
||||
import 'package:contacts_plus_plus/widgets/default_error_widget.dart';
|
||||
import 'package:contacts_plus_plus/widgets/expanding_input_fab.dart';
|
||||
import 'package:contacts_plus_plus/widgets/friend_list_tile.dart';
|
||||
import 'package:contacts_plus_plus/widgets/settings_page.dart';
|
||||
|
@ -41,12 +42,12 @@ class _FriendsListState extends State<FriendsList> {
|
|||
}
|
||||
|
||||
void _refreshFriendsList() {
|
||||
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.client).then((Iterable<Friend> value) async {
|
||||
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.client, unreadOnly: true);
|
||||
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.apiClient).then((Iterable<Friend> value) async {
|
||||
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.apiClient, unreadOnly: true);
|
||||
_unreads.clear();
|
||||
|
||||
for (final msg in unreadMessages) {
|
||||
if (msg.senderId != _clientHolder!.client.userId) {
|
||||
if (msg.senderId != _clientHolder!.apiClient.userId) {
|
||||
final value = _unreads[msg.senderId];
|
||||
if (value == null) {
|
||||
_unreads[msg.senderId] = [msg];
|
||||
|
@ -110,7 +111,7 @@ class _FriendsListState extends State<FriendsList> {
|
|||
onTap: () async {
|
||||
if (unread.isNotEmpty) {
|
||||
final readBatch = MarkReadBatch(
|
||||
senderId: _clientHolder!.client.userId,
|
||||
senderId: _clientHolder!.apiClient.userId,
|
||||
ids: unread.map((e) => e.id).toList(),
|
||||
readTime: DateTime.now(),
|
||||
);
|
||||
|
@ -125,18 +126,11 @@ class _FriendsListState extends State<FriendsList> {
|
|||
);
|
||||
} else if (snapshot.hasError) {
|
||||
FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace));
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(64),
|
||||
child: Text(
|
||||
"Something went wrong: ${snapshot.error}",
|
||||
softWrap: true,
|
||||
style: Theme
|
||||
.of(context)
|
||||
.textTheme
|
||||
.labelMedium,
|
||||
),
|
||||
),
|
||||
return DefaultErrorWidget(
|
||||
message: "${snapshot.error}",
|
||||
onRetry: () {
|
||||
_refreshFriendsList();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/models/authentication_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||
import 'package:contacts_plus_plus/models/message.dart';
|
||||
import 'package:contacts_plus_plus/widgets/messages.dart';
|
||||
import 'package:contacts_plus_plus/widgets/messages_list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
@ -114,7 +114,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
|||
),
|
||||
),
|
||||
const SizedBox(width: 4,),
|
||||
if (widget.message.senderId == ClientHolder.of(context).client.userId) Padding(
|
||||
if (widget.message.senderId == ClientHolder.of(context).apiClient.userId) Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: MessageStateIndicator(messageState: widget.message.state),
|
||||
),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||
import 'package:contacts_plus_plus/models/message.dart';
|
||||
import 'package:contacts_plus_plus/models/session.dart';
|
||||
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
||||
import 'package:contacts_plus_plus/widgets/messages.dart';
|
||||
import 'package:contacts_plus_plus/widgets/messages_list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
|
@ -68,7 +68,7 @@ class MessageSessionInvite extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
const SizedBox(width: 4,),
|
||||
if (message.senderId == ClientHolder.of(context).client.userId) Padding(
|
||||
if (message.senderId == ClientHolder.of(context).apiClient.userId) Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: MessageStateIndicator(messageState: message.state),
|
||||
),
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||
import 'package:contacts_plus_plus/models/friend.dart';
|
||||
import 'package:contacts_plus_plus/models/message.dart';
|
||||
import 'package:contacts_plus_plus/models/session.dart';
|
||||
import 'package:contacts_plus_plus/widgets/default_error_widget.dart';
|
||||
import 'package:contacts_plus_plus/widgets/message_audio_player.dart';
|
||||
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
||||
import 'package:contacts_plus_plus/widgets/message_session_invite.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class Messages extends StatefulWidget {
|
||||
const Messages({required this.friend, super.key});
|
||||
class MessagesList extends StatefulWidget {
|
||||
const MessagesList({required this.friend, super.key});
|
||||
|
||||
final Friend friend;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _MessagesState();
|
||||
State<StatefulWidget> createState() => _MessagesListState();
|
||||
}
|
||||
|
||||
class _MessagesState extends State<Messages> {
|
||||
class _MessagesListState extends State<MessagesList> {
|
||||
Future<MessageCache>? _messageCacheFuture;
|
||||
final TextEditingController _messageTextController = TextEditingController();
|
||||
final ScrollController _sessionListScrollController = ScrollController();
|
||||
|
@ -79,8 +78,6 @@ class _MessagesState extends State<Messages> {
|
|||
_messageScrollController.addListener(() {
|
||||
if (_messageScrollController.position.atEdge && _messageScrollController.position.pixels > 0 &&
|
||||
_messageScrollController.position.maxScrollExtent > 0 && _messageCacheFutureComplete) {
|
||||
log("Top edge hit.");
|
||||
|
||||
setState(() {
|
||||
_messageCacheFutureComplete = false;
|
||||
_messageCacheFuture = _clientHolder?.hub.getCache(widget.friend.id)
|
||||
|
@ -94,7 +91,7 @@ class _MessagesState extends State<Messages> {
|
|||
Widget build(BuildContext context) {
|
||||
final apiClient = ClientHolder
|
||||
.of(context)
|
||||
.client;
|
||||
.apiClient;
|
||||
var sessions = widget.friend.userStatus.activeSessions;
|
||||
final appBarColor = Theme
|
||||
.of(context)
|
||||
|
@ -195,38 +192,13 @@ class _MessagesState extends State<Messages> {
|
|||
},
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 64, vertical: 128,),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text("Failed to load messages:", style: Theme
|
||||
.of(context)
|
||||
.textTheme
|
||||
.titleMedium,),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text("${snapshot.error}"),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
return DefaultErrorWidget(
|
||||
message: "${snapshot.error}",
|
||||
onRetry: () {
|
||||
setState(() {
|
||||
_loadMessages();
|
||||
});
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16, horizontal: 16),
|
||||
),
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text("Retry"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Column(
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:contacts_plus_plus/api_client.dart';
|
||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
@ -38,7 +39,7 @@ class SettingsPage extends StatelessWidget {
|
|||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("No")),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await ClientHolder.of(context).client.logout(context);
|
||||
await ClientHolder.of(context).apiClient.logout(context);
|
||||
},
|
||||
child: const Text("Yes"),
|
||||
),
|
||||
|
@ -46,6 +47,35 @@ class SettingsPage extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
shape: Border(
|
||||
bottom: BorderSide(color: Theme.of(context).colorScheme.secondaryContainer, width: 0.5),
|
||||
top: BorderSide(color: Theme.of(context).colorScheme.secondaryContainer, width: 0.5)
|
||||
),
|
||||
trailing: const Icon(Icons.info_outline),
|
||||
title: const Text("About Contacts++"),
|
||||
onTap: () {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationVersion: "0.0.1",
|
||||
applicationIcon: InkWell(
|
||||
onTap: () async {
|
||||
if (!await launchUrl(Uri.parse("https://github.com/Nutcake/contacts-plus-plus"), mode: LaunchMode.externalApplication)) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Failed to open link.")));
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
constraints: const BoxConstraints(maxWidth: 64),
|
||||
child: Image.asset("assets/images/logo512.png"),
|
||||
),
|
||||
),
|
||||
applicationLegalese: "Created by Nutcake with love <3",
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -7,9 +7,13 @@
|
|||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
136
pubspec.lock
136
pubspec.lock
|
@ -1,6 +1,14 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -97,6 +105,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -150,6 +166,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "2876372952b65ca7f684e698eba22bda1cf581fa071dd30ba2f01900f507d0d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.0.0+1"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: "909bb95de05a2e793503a2437146285a2f600cd0b3f826e26b870a334d8586d7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "63235c42de5b6c99846969a27ad0209c401e6b77b0498939813725b5791c107c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
flutter_phoenix:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -400,6 +440,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -525,6 +573,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.2"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -541,6 +597,70 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.10"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.31"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.16"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -573,6 +693,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
workmanager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: workmanager
|
||||
sha256: e0be7e35d644643f164ee45d2ce14414f0e0fdde19456aa66065f35a0b1d2ea1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -581,6 +709,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.2"
|
||||
sdks:
|
||||
dart: ">=2.19.6 <3.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
|
|
|
@ -47,6 +47,9 @@ dependencies:
|
|||
html: ^0.15.2
|
||||
just_audio: ^0.9.32
|
||||
flutter_phoenix: ^1.1.1
|
||||
url_launcher: ^6.1.10
|
||||
workmanager: ^0.5.1
|
||||
flutter_local_notifications: ^14.0.0+1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -71,9 +74,8 @@ flutter:
|
|||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
assets:
|
||||
- assets/images/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:contacts_plus_plus/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const ContactsPlusPlus());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
|
@ -7,8 +7,11 @@
|
|||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
Loading…
Reference in a new issue