mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +00:00
start with downloading media
This commit is contained in:
parent
5cea69c224
commit
bae21c4738
15 changed files with 236 additions and 76 deletions
|
|
@ -1,5 +1,8 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/api_provider.dart';
|
import 'package:twonly/src/providers/api_provider.dart';
|
||||||
import 'package:twonly/src/providers/db_provider.dart';
|
import 'package:twonly/src/providers/db_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -17,7 +20,7 @@ late ApiProvider apiProvider;
|
||||||
void main() async {
|
void main() async {
|
||||||
final settingsController = SettingsController(SettingsService());
|
final settingsController = SettingsController(SettingsService());
|
||||||
|
|
||||||
// Load the user's peganreferred theme while the splash screen is displayed.
|
// Load the user's preferred theme while the splash screen is displayed.
|
||||||
// This prevents a sudden theme change when the app is first displayed.
|
// This prevents a sudden theme change when the app is first displayed.
|
||||||
await settingsController.loadSettings();
|
await settingsController.loadSettings();
|
||||||
|
|
||||||
|
|
@ -33,6 +36,11 @@ void main() async {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
Hive.init(dir.path);
|
||||||
|
|
||||||
|
await initMediaStorage();
|
||||||
|
|
||||||
dbProvider = DbProvider();
|
dbProvider = DbProvider();
|
||||||
// Database is just a file, so this will not block the loading of the app much
|
// Database is just a file, so this will not block the loading of the app much
|
||||||
await dbProvider.ready;
|
await dbProvider.ready;
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,31 @@ class InitialsAvatar extends StatelessWidget {
|
||||||
Color avatarColor = _getColorFromUsername(
|
Color avatarColor = _getColorFromUsername(
|
||||||
displayName, Theme.of(context).brightness == Brightness.dark);
|
displayName, Theme.of(context).brightness == Brightness.dark);
|
||||||
|
|
||||||
return CircleAvatar(
|
Widget child = Text(
|
||||||
backgroundColor: avatarColor,
|
initials,
|
||||||
radius: fontSize,
|
style: TextStyle(
|
||||||
child: Text(
|
color: _getTextColor(avatarColor),
|
||||||
initials,
|
fontWeight: FontWeight.normal,
|
||||||
style: TextStyle(
|
fontSize: fontSize,
|
||||||
color: _getTextColor(avatarColor),
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: fontSize,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool isPro = initials[0] == "T";
|
||||||
|
|
||||||
|
double proSize = (fontSize == null) ? 40 : (fontSize! * 2);
|
||||||
|
|
||||||
|
return isPro
|
||||||
|
? ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12.0), //or 15.0
|
||||||
|
child: Container(
|
||||||
|
height: proSize,
|
||||||
|
width: proSize,
|
||||||
|
color: avatarColor,
|
||||||
|
child: Center(child: child),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: CircleAvatar(
|
||||||
|
backgroundColor: avatarColor, radius: fontSize, child: child);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _getTextColor(Color color) {
|
Color _getTextColor(Color color) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
|
||||||
enum MessageSendState {
|
enum MessageSendState {
|
||||||
received,
|
received,
|
||||||
|
|
@ -11,37 +13,39 @@ enum MessageSendState {
|
||||||
|
|
||||||
class MessageSendStateIcon extends StatelessWidget {
|
class MessageSendStateIcon extends StatelessWidget {
|
||||||
final MessageSendState state;
|
final MessageSendState state;
|
||||||
|
final MessageKind kind;
|
||||||
|
final bool isDownloaded;
|
||||||
|
|
||||||
const MessageSendStateIcon(this.state, {super.key});
|
const MessageSendStateIcon(this.state, this.isDownloaded, this.kind,
|
||||||
|
{super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget icon = Placeholder();
|
Widget icon = Placeholder();
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
|
Color color = Theme.of(context).colorScheme.primary;
|
||||||
|
if (kind == MessageKind.textMessage) {
|
||||||
|
color = Colors.lightBlue;
|
||||||
|
} else if (kind == MessageKind.video) {
|
||||||
|
color = Colors.deepPurple;
|
||||||
|
}
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case MessageSendState.receivedOpened:
|
case MessageSendState.receivedOpened:
|
||||||
|
icon = Icon(Icons.crop_square, size: 14, color: color);
|
||||||
|
text = "Received";
|
||||||
|
break;
|
||||||
case MessageSendState.sendOpened:
|
case MessageSendState.sendOpened:
|
||||||
icon = Icon(
|
icon = FaIcon(FontAwesomeIcons.paperPlane, size: 12, color: color);
|
||||||
Icons.crop_square,
|
|
||||||
size: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
);
|
|
||||||
text = "Opened";
|
text = "Opened";
|
||||||
break;
|
break;
|
||||||
case MessageSendState.received:
|
case MessageSendState.received:
|
||||||
icon = Icon(
|
icon = Icon(Icons.square_rounded, size: 14, color: color);
|
||||||
Icons.square_rounded,
|
|
||||||
size: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
);
|
|
||||||
text = "Received";
|
text = "Received";
|
||||||
break;
|
break;
|
||||||
case MessageSendState.send:
|
case MessageSendState.send:
|
||||||
icon = Icon(
|
icon = FaIcon(FontAwesomeIcons.solidPaperPlane, size: 12, color: color);
|
||||||
Icons.send,
|
|
||||||
size: 14,
|
|
||||||
);
|
|
||||||
text = "Send";
|
text = "Send";
|
||||||
break;
|
break;
|
||||||
case MessageSendState.sending:
|
case MessageSendState.sending:
|
||||||
|
|
@ -51,9 +55,7 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 10,
|
height: 10,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(strokeWidth: 1, color: color),
|
||||||
strokeWidth: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(width: 2),
|
SizedBox(width: 2),
|
||||||
],
|
],
|
||||||
|
|
@ -62,6 +64,10 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDownloaded) {
|
||||||
|
text = "Tap do load";
|
||||||
|
}
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
icon,
|
icon,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:pie_menu/pie_menu.dart';
|
import 'package:pie_menu/pie_menu.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
|
||||||
|
|
@ -22,7 +23,6 @@ class _UserContextMenuState extends State<UserContextMenu> {
|
||||||
tooltip: const Text('Verify user'),
|
tooltip: const Text('Verify user'),
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
print('Verify user selected');
|
print('Verify user selected');
|
||||||
// Add your verification logic here
|
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.gpp_maybe_rounded), // Can be any widget
|
child: const Icon(Icons.gpp_maybe_rounded), // Can be any widget
|
||||||
),
|
),
|
||||||
|
|
@ -30,9 +30,8 @@ class _UserContextMenuState extends State<UserContextMenu> {
|
||||||
tooltip: const Text('Send image'),
|
tooltip: const Text('Send image'),
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
print('Send image selected');
|
print('Send image selected');
|
||||||
// Add your image sending logic here
|
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.camera_alt_rounded), // Can be any widget
|
child: const FaIcon(FontAwesomeIcons.camera),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class DbMessage {
|
||||||
required this.messageId,
|
required this.messageId,
|
||||||
required this.messageOtherId,
|
required this.messageOtherId,
|
||||||
required this.otherUserId,
|
required this.otherUserId,
|
||||||
required this.messageMessageKind,
|
required this.messageKind,
|
||||||
required this.messageContent,
|
required this.messageContent,
|
||||||
required this.messageOpenedAt,
|
required this.messageOpenedAt,
|
||||||
required this.messageAcknowledgeByUser,
|
required this.messageAcknowledgeByUser,
|
||||||
|
|
@ -23,12 +23,17 @@ class DbMessage {
|
||||||
// is this null then the message was sent from the user itself
|
// is this null then the message was sent from the user itself
|
||||||
int? messageOtherId;
|
int? messageOtherId;
|
||||||
int otherUserId;
|
int otherUserId;
|
||||||
MessageKind messageMessageKind;
|
MessageKind messageKind;
|
||||||
MessageContent? messageContent;
|
MessageContent? messageContent;
|
||||||
DateTime? messageOpenedAt;
|
DateTime? messageOpenedAt;
|
||||||
bool messageAcknowledgeByUser;
|
bool messageAcknowledgeByUser;
|
||||||
bool messageAcknowledgeByServer;
|
bool messageAcknowledgeByServer;
|
||||||
DateTime sendOrReceivedAt;
|
DateTime sendOrReceivedAt;
|
||||||
|
|
||||||
|
bool containsOtherMedia() {
|
||||||
|
if (messageOtherId == null) return false;
|
||||||
|
return messageKind == MessageKind.image || messageKind == MessageKind.video;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DbMessages extends CvModelBase {
|
class DbMessages extends CvModelBase {
|
||||||
|
|
@ -44,7 +49,7 @@ class DbMessages extends CvModelBase {
|
||||||
final otherUserId = CvField<int>(columnOtherUserId);
|
final otherUserId = CvField<int>(columnOtherUserId);
|
||||||
|
|
||||||
static const columnMessageKind = "message_kind";
|
static const columnMessageKind = "message_kind";
|
||||||
final messageMessageKind = CvField<int>(columnMessageKind);
|
final messageKind = CvField<int>(columnMessageKind);
|
||||||
|
|
||||||
static const columnMessageContentJson = "message_json";
|
static const columnMessageContentJson = "message_json";
|
||||||
final messageContentJson = CvField<String?>(columnMessageContentJson);
|
final messageContentJson = CvField<String?>(columnMessageContentJson);
|
||||||
|
|
@ -135,7 +140,8 @@ class DbMessages extends CvModelBase {
|
||||||
columnMessageAcknowledgeByServer: 1,
|
columnMessageAcknowledgeByServer: 1,
|
||||||
columnMessageAcknowledgeByUser:
|
columnMessageAcknowledgeByUser:
|
||||||
0, // ack in case of sending corresponds to the opened flag
|
0, // ack in case of sending corresponds to the opened flag
|
||||||
columnOtherUserId: userIdFrom
|
columnOtherUserId: userIdFrom,
|
||||||
|
columnSendOrReceivedAt: DateTime.now().toIso8601String()
|
||||||
});
|
});
|
||||||
globalCallBackOnMessageChange(userIdFrom);
|
globalCallBackOnMessageChange(userIdFrom);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -204,7 +210,7 @@ class DbMessages extends CvModelBase {
|
||||||
@override
|
@override
|
||||||
List<CvField> get fields => [
|
List<CvField> get fields => [
|
||||||
messageId,
|
messageId,
|
||||||
messageMessageKind,
|
messageKind,
|
||||||
messageContentJson,
|
messageContentJson,
|
||||||
messageOpenedAt,
|
messageOpenedAt,
|
||||||
sendOrReceivedAt
|
sendOrReceivedAt
|
||||||
|
|
@ -230,7 +236,7 @@ class DbMessages extends CvModelBase {
|
||||||
messageId: fromDb[i][columnMessageId],
|
messageId: fromDb[i][columnMessageId],
|
||||||
messageOtherId: fromDb[i][columnMessageOtherId],
|
messageOtherId: fromDb[i][columnMessageOtherId],
|
||||||
otherUserId: fromDb[i][columnOtherUserId],
|
otherUserId: fromDb[i][columnOtherUserId],
|
||||||
messageMessageKind:
|
messageKind:
|
||||||
MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]),
|
MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]),
|
||||||
messageContent: content,
|
messageContent: content,
|
||||||
messageOpenedAt: messageOpenedAt,
|
messageOpenedAt: messageOpenedAt,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/main.dart';
|
import 'package:twonly/main.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
|
@ -104,4 +106,42 @@ Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
||||||
print("check if free network connection");
|
print("check if free network connection");
|
||||||
|
|
||||||
print("Downloading: " + imageToken.toString());
|
print("Downloading: " + imageToken.toString());
|
||||||
|
|
||||||
|
final box = await getMediaStorage();
|
||||||
|
|
||||||
|
Uint8List imageBytes = Uint8List.fromList([0]);
|
||||||
|
|
||||||
|
box.put(imageToken.toString(), imageBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isMediaDownloaded(List<int> mediaToken) async {
|
||||||
|
final box = await getMediaStorage();
|
||||||
|
|
||||||
|
// box.put('secret', 'Hive is awesome');
|
||||||
|
|
||||||
|
return box.containsKey(mediaToken.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future initMediaStorage() async {
|
||||||
|
final storage = getSecureStorage();
|
||||||
|
var containsEncryptionKey =
|
||||||
|
await storage.containsKey(key: 'hive_encryption_key');
|
||||||
|
if (!containsEncryptionKey) {
|
||||||
|
var key = Hive.generateSecureKey();
|
||||||
|
await storage.write(
|
||||||
|
key: 'hive_encryption_key',
|
||||||
|
value: base64UrlEncode(key),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Box> getMediaStorage() async {
|
||||||
|
await initMediaStorage();
|
||||||
|
|
||||||
|
final storage = getSecureStorage();
|
||||||
|
var encryptionKey =
|
||||||
|
base64Url.decode((await storage.read(key: 'hive_encryption_key'))!);
|
||||||
|
|
||||||
|
return await Hive.openBox('media_storage',
|
||||||
|
encryptionCipher: HiveAesCipher(encryptionKey));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,9 @@ class ApiProvider {
|
||||||
|
|
||||||
Future<Result> _sendRequestV0(ClientToServer request) async {
|
Future<Result> _sendRequestV0(ClientToServer request) async {
|
||||||
if (_channel == null) {
|
if (_channel == null) {
|
||||||
return Result.error(ErrorCode.InternalError);
|
if (!await connect()) {
|
||||||
|
return Result.error(ErrorCode.InternalError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var seq = Int64(Random().nextInt(4294967296));
|
var seq = Int64(Random().nextInt(4294967296));
|
||||||
while (messagesV0.containsKey(seq)) {
|
while (messagesV0.containsKey(seq)) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
|
||||||
class AlignedTextBox extends StatelessWidget {
|
class AlignedTextBox extends StatelessWidget {
|
||||||
|
|
@ -39,8 +40,8 @@ class AlignedTextBox extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays detailed information about a SampleItem.
|
/// Displays detailed information about a SampleItem.
|
||||||
class SampleItemDetailsView extends StatelessWidget {
|
class ChatItemDetailsView extends StatelessWidget {
|
||||||
const SampleItemDetailsView({super.key, required this.user});
|
const ChatItemDetailsView({super.key, required this.user});
|
||||||
|
|
||||||
final Contact user;
|
final Contact user;
|
||||||
|
|
||||||
|
|
@ -86,24 +87,50 @@ class SampleItemDetailsView extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 40, left: 10, right: 10),
|
padding:
|
||||||
child: TextField(
|
const EdgeInsets.only(bottom: 30, left: 20, right: 20, top: 10),
|
||||||
decoration: InputDecoration(
|
child: Row(
|
||||||
// border: OutlineInputBorder(),
|
children: [
|
||||||
labelText: 'Enter your message',
|
Expanded(
|
||||||
suffixIcon: IconButton(
|
child: TextField(
|
||||||
icon: Icon(Icons.send),
|
// controller: _controller,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Type a message',
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 10)
|
||||||
|
// border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
IconButton(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Handle send action
|
// Handle send action
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Container(
|
||||||
|
// child: Row(children: [
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.only(bottom: 40, left: 10, right: 10),
|
||||||
|
// child: TextField(
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// // border: OutlineInputBorder(),
|
||||||
|
// hintText: 'Enter your message',
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// IconButton(
|
||||||
|
// icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
|
// onPressed: () {
|
||||||
|
// // Handle send action
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ]),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import 'package:twonly/src/components/notification_badge.dart';
|
||||||
import 'package:twonly/src/components/user_context_menu.dart';
|
import 'package:twonly/src/components/user_context_menu.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/chat_item_details_view.dart';
|
import 'package:twonly/src/views/chat_item_details_view.dart';
|
||||||
import 'package:twonly/src/views/home_view.dart';
|
import 'package:twonly/src/views/home_view.dart';
|
||||||
|
import 'package:twonly/src/views/media_viewer_view.dart';
|
||||||
import 'package:twonly/src/views/search_username_view.dart';
|
import 'package:twonly/src/views/search_username_view.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -47,6 +49,11 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
List<Contact> activeUsers = allUsers
|
List<Contact> activeUsers = allUsers
|
||||||
.where((x) => lastMessages.containsKey(x.userId.toInt()))
|
.where((x) => lastMessages.containsKey(x.userId.toInt()))
|
||||||
.toList();
|
.toList();
|
||||||
|
activeUsers.sort((b, a) {
|
||||||
|
return lastMessages[a.userId.toInt()]!
|
||||||
|
.sendOrReceivedAt
|
||||||
|
.compareTo(lastMessages[b.userId.toInt()]!.sendOrReceivedAt);
|
||||||
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -125,24 +132,28 @@ class UserListItem extends StatefulWidget {
|
||||||
class _UserListItem extends State<UserListItem> {
|
class _UserListItem extends State<UserListItem> {
|
||||||
int flames = 0;
|
int flames = 0;
|
||||||
int lastMessageInSeconds = 0;
|
int lastMessageInSeconds = 0;
|
||||||
|
bool isDownloaded = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
//_loadAsync();
|
_loadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future _loadAsync() async {
|
Future _loadAsync() async {
|
||||||
// flames = await widget.user.getFlames();
|
// flames = await widget.user.getFlames();
|
||||||
// lastMessageInSeconds = await widget.user.getLastMessageInSeconds();
|
// setState(() {});
|
||||||
// setState(() {});
|
|
||||||
// }
|
if (widget.lastMessage.containsOtherMedia()) {
|
||||||
|
isDownloaded = await isMediaDownloaded(
|
||||||
|
widget.lastMessage.messageContent!.downloadToken!);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
MessageSendState state;
|
MessageSendState state;
|
||||||
// int lastMessageInSeconds = widget.lastMessage.sendOrReceivedAt;
|
|
||||||
//print(widget.lastMessage.sendOrReceivedAt);
|
|
||||||
int lastMessageInSeconds = DateTime.now()
|
int lastMessageInSeconds = DateTime.now()
|
||||||
.difference(widget.lastMessage.sendOrReceivedAt)
|
.difference(widget.lastMessage.sendOrReceivedAt)
|
||||||
.inSeconds;
|
.inSeconds;
|
||||||
|
|
@ -173,7 +184,8 @@ class _UserListItem extends State<UserListItem> {
|
||||||
title: Text(widget.user.displayName),
|
title: Text(widget.user.displayName),
|
||||||
subtitle: Row(
|
subtitle: Row(
|
||||||
children: [
|
children: [
|
||||||
MessageSendStateIcon(state),
|
MessageSendStateIcon(
|
||||||
|
state, isDownloaded, widget.lastMessage.messageKind),
|
||||||
Text("•"),
|
Text("•"),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -203,11 +215,19 @@ class _UserListItem extends State<UserListItem> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(builder: (context) {
|
||||||
builder: (context) => SampleItemDetailsView(
|
if (state == MessageSendState.received &&
|
||||||
user: widget.user,
|
widget.lastMessage.containsOtherMedia()) {
|
||||||
),
|
List<int> token =
|
||||||
),
|
widget.lastMessage.messageContent!.downloadToken!;
|
||||||
|
if (isDownloaded) {
|
||||||
|
return MediaViewerView(widget.user);
|
||||||
|
} else {
|
||||||
|
tryDownloadMedia(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ChatItemDetailsView(user: widget.user);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:pie_menu/pie_menu.dart';
|
import 'package:pie_menu/pie_menu.dart';
|
||||||
import 'camera_preview_view.dart';
|
import 'camera_preview_view.dart';
|
||||||
import 'chat_list_view.dart';
|
import 'chat_list_view.dart';
|
||||||
|
|
@ -79,12 +80,14 @@ class HomeViewState extends State<HomeView> {
|
||||||
selectedIconTheme:
|
selectedIconTheme:
|
||||||
IconThemeData(color: const Color.fromARGB(255, 255, 255, 255)),
|
IconThemeData(color: const Color.fromARGB(255, 255, 255, 255)),
|
||||||
items: [
|
items: [
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.chat), label: ""),
|
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.camera_alt),
|
icon: FaIcon(FontAwesomeIcons.solidComments), label: ""),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.camera),
|
||||||
label: "",
|
label: "",
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.verified_user), label: ""),
|
BottomNavigationBarItem(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.userShield), label: ""),
|
||||||
],
|
],
|
||||||
onTap: (int index) {
|
onTap: (int index) {
|
||||||
activePageIdx = index;
|
activePageIdx = index;
|
||||||
|
|
|
||||||
19
lib/src/views/media_viewer_view.dart
Normal file
19
lib/src/views/media_viewer_view.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
|
||||||
|
class MediaViewerView extends StatefulWidget {
|
||||||
|
final Contact otherUser;
|
||||||
|
const MediaViewerView(this.otherUser, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MediaViewerView> createState() => _MediaViewerViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Text(widget.otherUser.displayName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/share_image_view.dart';
|
import 'package:twonly/src/views/share_image_view.dart';
|
||||||
|
|
||||||
|
|
@ -63,10 +64,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(Icons.close, size: 30),
|
||||||
Icons.close,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
@ -87,7 +85,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
icon: _imageSaved
|
icon: _imageSaved
|
||||||
? Icon(Icons.check)
|
? Icon(Icons.check)
|
||||||
: Icon(Icons.save_rounded),
|
: FaIcon(FontAwesomeIcons.floppyDisk),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
iconColor: _imageSaved
|
iconColor: _imageSaved
|
||||||
? Theme.of(context).colorScheme.outline
|
? Theme.of(context).colorScheme.outline
|
||||||
|
|
@ -113,7 +111,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: Icon(Icons.send),
|
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:collection';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/components/best_friends_selector.dart';
|
import 'package:twonly/src/components/best_friends_selector.dart';
|
||||||
import 'package:twonly/src/components/headline.dart';
|
import 'package:twonly/src/components/headline.dart';
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
|
|
@ -107,7 +108,7 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: Icon(Icons.send),
|
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
sendImage(_selectedUserIds.toList(), widget.image);
|
sendImage(_selectedUserIds.toList(), widget.image);
|
||||||
|
|
||||||
|
|
|
||||||
16
pubspec.lock
16
pubspec.lock
|
|
@ -490,6 +490,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
font_awesome_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: font_awesome_flutter
|
||||||
|
sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.8.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -530,6 +538,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
hive:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive
|
||||||
|
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,10 @@ dependencies:
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_secure_storage: ^9.2.2
|
flutter_secure_storage: ^9.2.2
|
||||||
|
font_awesome_flutter: ^10.8.0
|
||||||
gal: ^2.3.1
|
gal: ^2.3.1
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
|
hive: ^2.2.3
|
||||||
image: ^4.3.0
|
image: ^4.3.0
|
||||||
intl: any
|
intl: any
|
||||||
introduction_screen: ^3.1.14
|
introduction_screen: ^3.1.14
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue