mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-18 14:42:54 +00:00
improved websocket connection state info
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
587740f306
commit
527bf51bff
11 changed files with 283 additions and 222 deletions
|
|
@ -31,7 +31,7 @@ import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.
|
||||||
import 'package:twonly/src/views/camera/share_image_editor.view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor.view.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/home.view.dart';
|
import 'package:twonly/src/views/home.view.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/c
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
||||||
|
|
||||||
class LinkPreviewLayer extends StatefulWidget {
|
class LinkPreviewLayer extends StatefulWidget {
|
||||||
const LinkPreviewLayer({
|
const LinkPreviewLayer({
|
||||||
|
|
@ -32,8 +32,9 @@ class _LinkPreviewLayerState extends State<LinkPreviewLayer> {
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
if (widget.layerData.metadata == null) {
|
if (widget.layerData.metadata == null) {
|
||||||
widget.layerData.metadata =
|
widget.layerData.metadata = await getMetadata(
|
||||||
await getMetadata(widget.layerData.link.toString());
|
widget.layerData.link.toString(),
|
||||||
|
);
|
||||||
if (widget.layerData.metadata == null) {
|
if (widget.layerData.metadata == null) {
|
||||||
widget.layerData.error = true;
|
widget.layerData.error = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
||||||
|
|
||||||
class MastodonPostCard extends StatelessWidget {
|
class MastodonPostCard extends StatelessWidget {
|
||||||
const MastodonPostCard({required this.info, super.key});
|
const MastodonPostCard({required this.info, super.key});
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,14 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
|
||||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart';
|
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart';
|
import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/group_list_item.dart';
|
import 'package:twonly/src/views/chats/chat_list_components/group_list_item.dart';
|
||||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||||
|
import 'package:twonly/src/views/components/connection_status_badge.dart';
|
||||||
import 'package:twonly/src/views/components/notification_badge.dart';
|
import 'package:twonly/src/views/components/notification_badge.dart';
|
||||||
|
|
||||||
class ChatListView extends StatefulWidget {
|
class ChatListView extends StatefulWidget {
|
||||||
|
|
@ -45,8 +44,9 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
final stream = twonlyDB.groupsDao.watchGroupsForChatList();
|
final stream = twonlyDB.groupsDao.watchGroupsForChatList();
|
||||||
_contactsSub = stream.listen((groups) {
|
_contactsSub = stream.listen((groups) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_groupsNotPinned =
|
_groupsNotPinned = groups
|
||||||
groups.where((x) => !x.pinned && !x.archived).toList();
|
.where((x) => !x.pinned && !x.archived)
|
||||||
|
.toList();
|
||||||
_groupsPinned = groups.where((x) => x.pinned && !x.archived).toList();
|
_groupsPinned = groups.where((x) => x.pinned && !x.archived).toList();
|
||||||
_groupsArchived = groups.where((x) => x.archived).toList();
|
_groupsArchived = groups.where((x) => x.archived).toList();
|
||||||
});
|
});
|
||||||
|
|
@ -64,8 +64,10 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final changeLog = await rootBundle.loadString('CHANGELOG.md');
|
final changeLog = await rootBundle.loadString('CHANGELOG.md');
|
||||||
final changeLogHash =
|
final changeLogHash = (await compute(
|
||||||
(await compute(Sha256().hash, changeLog.codeUnits)).bytes;
|
Sha256().hash,
|
||||||
|
changeLog.codeUnits,
|
||||||
|
)).bytes;
|
||||||
if (!gUser.hideChangeLog &&
|
if (!gUser.hideChangeLog &&
|
||||||
gUser.lastChangeLogHash.toString() != changeLogHash.toString()) {
|
gUser.lastChangeLogHash.toString() != changeLogHash.toString()) {
|
||||||
await updateUserdata((u) {
|
await updateUserdata((u) {
|
||||||
|
|
@ -93,22 +95,23 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isConnected = context.watch<CustomChangeProvider>().isConnected;
|
|
||||||
final plan = context.watch<PurchasesProvider>().plan;
|
final plan = context.watch<PurchasesProvider>().plan;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
ConnectionStatusBadge(
|
||||||
onTap: () async {
|
child: GestureDetector(
|
||||||
await context.push(Routes.settingsProfile);
|
onTap: () async {
|
||||||
if (!mounted) return;
|
await context.push(Routes.settingsProfile);
|
||||||
setState(() {}); // gUser has updated
|
if (!mounted) return;
|
||||||
},
|
setState(() {}); // gUser has updated
|
||||||
child: AvatarIcon(
|
},
|
||||||
myAvatar: true,
|
child: AvatarIcon(
|
||||||
fontSize: 14,
|
myAvatar: true,
|
||||||
color: context.color.onSurface.withAlpha(20),
|
fontSize: 14,
|
||||||
|
color: context.color.onSurface.withAlpha(20),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
|
|
@ -121,8 +124,10 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
color: context.color.primary,
|
color: context.color.primary,
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
|
horizontal: 5,
|
||||||
|
vertical: 3,
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
plan.name,
|
plan.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -163,87 +168,77 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: RefreshIndicator(
|
||||||
children: [
|
onRefresh: () async {
|
||||||
Positioned(
|
await apiService.close(() {});
|
||||||
top: 0,
|
await apiService.connect();
|
||||||
left: 0,
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
right: 0,
|
},
|
||||||
child: isConnected ? Container() : const ConnectionInfo(),
|
child:
|
||||||
),
|
(_groupsNotPinned.isEmpty &&
|
||||||
Positioned.fill(
|
_groupsPinned.isEmpty &&
|
||||||
child: RefreshIndicator(
|
_groupsArchived.isEmpty)
|
||||||
onRefresh: () async {
|
? Center(
|
||||||
await apiService.close(() {});
|
child: Padding(
|
||||||
await apiService.connect();
|
padding: const EdgeInsets.all(10),
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
child: OutlinedButton.icon(
|
||||||
},
|
icon: const Icon(Icons.person_add),
|
||||||
child: (_groupsNotPinned.isEmpty &&
|
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||||
_groupsPinned.isEmpty &&
|
label: Text(
|
||||||
_groupsArchived.isEmpty)
|
context.lang.chatListViewSearchUserNameBtn,
|
||||||
? Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
icon: const Icon(Icons.person_add),
|
|
||||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
|
||||||
label: Text(
|
|
||||||
context.lang.chatListViewSearchUserNameBtn,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.builder(
|
|
||||||
itemCount: _groupsPinned.length +
|
|
||||||
(_groupsPinned.isNotEmpty ? 1 : 0) +
|
|
||||||
_groupsNotPinned.length +
|
|
||||||
(_groupsArchived.isNotEmpty ? 1 : 0),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
if (index >=
|
|
||||||
_groupsNotPinned.length +
|
|
||||||
_groupsPinned.length +
|
|
||||||
(_groupsPinned.isNotEmpty ? 1 : 0)) {
|
|
||||||
if (_groupsArchived.isEmpty) return Container();
|
|
||||||
return ListTile(
|
|
||||||
title: Text(
|
|
||||||
'${context.lang.archivedChats} (${_groupsArchived.length})',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: const TextStyle(fontSize: 13),
|
|
||||||
),
|
|
||||||
onTap: () => context.push(Routes.chatsArchived),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Check if the index is for the pinned users
|
|
||||||
if (index < _groupsPinned.length) {
|
|
||||||
final group = _groupsPinned[index];
|
|
||||||
return GroupListItem(
|
|
||||||
key: ValueKey(group.groupId),
|
|
||||||
group: group,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are pinned users, account for the Divider
|
|
||||||
var adjustedIndex = index - _groupsPinned.length;
|
|
||||||
if (_groupsPinned.isNotEmpty && adjustedIndex == 0) {
|
|
||||||
return const Divider();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the index for the contacts list
|
|
||||||
adjustedIndex -= (_groupsPinned.isNotEmpty ? 1 : 0);
|
|
||||||
|
|
||||||
// Get the contacts that are not pinned
|
|
||||||
final group = _groupsNotPinned.elementAt(
|
|
||||||
adjustedIndex,
|
|
||||||
);
|
|
||||||
return GroupListItem(
|
|
||||||
key: ValueKey(group.groupId),
|
|
||||||
group: group,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount:
|
||||||
|
_groupsPinned.length +
|
||||||
|
(_groupsPinned.isNotEmpty ? 1 : 0) +
|
||||||
|
_groupsNotPinned.length +
|
||||||
|
(_groupsArchived.isNotEmpty ? 1 : 0),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index >=
|
||||||
|
_groupsNotPinned.length +
|
||||||
|
_groupsPinned.length +
|
||||||
|
(_groupsPinned.isNotEmpty ? 1 : 0)) {
|
||||||
|
if (_groupsArchived.isEmpty) return Container();
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
'${context.lang.archivedChats} (${_groupsArchived.length})',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
onTap: () => context.push(Routes.chatsArchived),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Check if the index is for the pinned users
|
||||||
|
if (index < _groupsPinned.length) {
|
||||||
|
final group = _groupsPinned[index];
|
||||||
|
return GroupListItem(
|
||||||
|
key: ValueKey(group.groupId),
|
||||||
|
group: group,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are pinned users, account for the Divider
|
||||||
|
var adjustedIndex = index - _groupsPinned.length;
|
||||||
|
if (_groupsPinned.isNotEmpty && adjustedIndex == 0) {
|
||||||
|
return const Divider();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the index for the contacts list
|
||||||
|
adjustedIndex -= (_groupsPinned.isNotEmpty ? 1 : 0);
|
||||||
|
|
||||||
|
// Get the contacts that are not pinned
|
||||||
|
final group = _groupsNotPinned.elementAt(
|
||||||
|
adjustedIndex,
|
||||||
|
);
|
||||||
|
return GroupListItem(
|
||||||
|
key: ValueKey(group.groupId),
|
||||||
|
group: group,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: Padding(
|
floatingActionButton: Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 30),
|
padding: const EdgeInsets.only(bottom: 30),
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
|
|
||||||
class ConnectionInfo extends StatefulWidget {
|
|
||||||
const ConnectionInfo({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ConnectionInfo> createState() => _ConnectionInfoWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConnectionInfoWidgetState extends State<ConnectionInfo>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
late AnimationController _controller;
|
|
||||||
late Animation<double> _positionAnim;
|
|
||||||
late Animation<double> _widthAnim;
|
|
||||||
|
|
||||||
bool showAnimation = false;
|
|
||||||
|
|
||||||
final double minBarWidth = 40;
|
|
||||||
final double maxBarWidth = 150;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_controller = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(seconds: 4),
|
|
||||||
);
|
|
||||||
|
|
||||||
_positionAnim = Tween<double>(begin: 0, end: 1).animate(
|
|
||||||
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
|
|
||||||
);
|
|
||||||
|
|
||||||
_widthAnim = TweenSequence([
|
|
||||||
TweenSequenceItem(
|
|
||||||
tween: Tween<double>(begin: minBarWidth, end: maxBarWidth),
|
|
||||||
weight: 50,
|
|
||||||
),
|
|
||||||
TweenSequenceItem(
|
|
||||||
tween: Tween<double>(begin: maxBarWidth, end: minBarWidth),
|
|
||||||
weight: 50,
|
|
||||||
),
|
|
||||||
]).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
|
||||||
|
|
||||||
// Delay start by 2 seconds
|
|
||||||
Future.delayed(const Duration(seconds: 2), () {
|
|
||||||
if (mounted) {
|
|
||||||
unawaited(_controller.repeat(reverse: true));
|
|
||||||
setState(() {
|
|
||||||
showAnimation = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (!showAnimation) return Container();
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
width: screenWidth,
|
|
||||||
height: 1,
|
|
||||||
child: AnimatedBuilder(
|
|
||||||
animation: _controller,
|
|
||||||
builder: (context, child) {
|
|
||||||
final barWidth = _widthAnim.value;
|
|
||||||
final left = _positionAnim.value * (screenWidth - barWidth);
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
left: left,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Container(
|
|
||||||
width: barWidth,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: context.color.primary,
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,7 @@ import 'package:twonly/src/views/camera/camera_send_to.view.dart';
|
||||||
import 'package:twonly/src/views/chats/media_viewer_components/additional_message_content.dart';
|
import 'package:twonly/src/views/chats/media_viewer_components/additional_message_content.dart';
|
||||||
import 'package:twonly/src/views/chats/media_viewer_components/reaction_buttons.component.dart';
|
import 'package:twonly/src/views/chats/media_viewer_components/reaction_buttons.component.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
|
|
||||||
43
lib/src/views/components/connection_status_badge.dart
Normal file
43
lib/src/views/components/connection_status_badge.dart
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/components/loader/ripple.loader.dart';
|
||||||
|
|
||||||
|
class ConnectionStatusBadge extends StatelessWidget {
|
||||||
|
const ConnectionStatusBadge({
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isConnected = context.watch<CustomChangeProvider>().isConnected;
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
if (!isConnected)
|
||||||
|
const Positioned.fill(
|
||||||
|
child: SpinKitRipple(
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: isConnected
|
||||||
|
? context.color.primary.withAlpha(100)
|
||||||
|
: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(0.5),
|
||||||
|
child: Center(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
lib/src/views/components/loader/ripple.loader.dart
Normal file
114
lib/src/views/components/loader/ripple.loader.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
// FROM: https://github.com/jogboms/flutter_spinkit/blob/master/lib/src/ripple.dart
|
||||||
|
|
||||||
|
// ignore_for_file: prefer_int_literals
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SpinKitRipple extends StatefulWidget {
|
||||||
|
const SpinKitRipple({
|
||||||
|
super.key,
|
||||||
|
this.color,
|
||||||
|
this.size = 50.0,
|
||||||
|
this.borderWidth = 6.0,
|
||||||
|
this.itemBuilder,
|
||||||
|
this.duration = const Duration(milliseconds: 1800),
|
||||||
|
this.controller,
|
||||||
|
}) : assert(
|
||||||
|
!(itemBuilder is IndexedWidgetBuilder && color is Color) &&
|
||||||
|
!(itemBuilder == null && color == null),
|
||||||
|
'You should specify either a itemBuilder or a color',
|
||||||
|
);
|
||||||
|
|
||||||
|
final Color? color;
|
||||||
|
final double size;
|
||||||
|
final double borderWidth;
|
||||||
|
final IndexedWidgetBuilder? itemBuilder;
|
||||||
|
final Duration duration;
|
||||||
|
final AnimationController? controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SpinKitRipple> createState() => _SpinKitRippleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SpinKitRippleState extends State<SpinKitRipple>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _animation1;
|
||||||
|
late Animation<double> _animation2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_controller =
|
||||||
|
(widget.controller ??
|
||||||
|
AnimationController(vsync: this, duration: widget.duration))
|
||||||
|
..addListener(() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
..repeat();
|
||||||
|
_animation1 = Tween(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _controller,
|
||||||
|
curve: const Interval(0.0, 0.75),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_animation2 = Tween(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _controller,
|
||||||
|
curve: const Interval(0.25, 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (widget.controller == null) {
|
||||||
|
_controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Opacity(
|
||||||
|
opacity: 1.0 - _animation1.value,
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: _animation1.value,
|
||||||
|
child: _itemBuilder(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Opacity(
|
||||||
|
opacity: 1.0 - _animation2.value,
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: _animation2.value,
|
||||||
|
child: _itemBuilder(1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _itemBuilder(int index) {
|
||||||
|
return SizedBox.fromSize(
|
||||||
|
size: Size.square(widget.size),
|
||||||
|
child: widget.itemBuilder != null
|
||||||
|
? widget.itemBuilder!(context, index)
|
||||||
|
: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: widget.color!,
|
||||||
|
width: widget.borderWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
||||||
|
|
||||||
|
|
@ -43,8 +43,8 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
final nonHashedFiles =
|
final nonHashedFiles = await twonlyDB.mediaFilesDao
|
||||||
await twonlyDB.mediaFilesDao.getAllNonHashedStoredMediaFiles();
|
.getAllNonHashedStoredMediaFiles();
|
||||||
if (nonHashedFiles.isNotEmpty) {
|
if (nonHashedFiles.isNotEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_filesToMigrate = nonHashedFiles.length;
|
_filesToMigrate = nonHashedFiles.length;
|
||||||
|
|
@ -100,8 +100,9 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
for (var i = 0; i < galleryItems.length; i++) {
|
for (var i = 0; i < galleryItems.length; i++) {
|
||||||
final month = DateFormat('MMMM yyyy')
|
final month = DateFormat(
|
||||||
.format(galleryItems[i].mediaService.mediaFile.createdAt);
|
'MMMM yyyy',
|
||||||
|
).format(galleryItems[i].mediaService.mediaFile.createdAt);
|
||||||
if (lastMonth != month) {
|
if (lastMonth != month) {
|
||||||
lastMonth = month;
|
lastMonth = month;
|
||||||
months.add(month);
|
months.add(month);
|
||||||
|
|
@ -259,15 +260,16 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
int index,
|
int index,
|
||||||
) async {
|
) async {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView(
|
pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView(
|
||||||
galleryItems: galleryItems,
|
galleryItems: galleryItems,
|
||||||
initialIndex: index,
|
initialIndex: index,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
) as bool?;
|
)
|
||||||
|
as bool?;
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
||||||
|
|
||||||
class DiagnosticsView extends StatefulWidget {
|
class DiagnosticsView extends StatefulWidget {
|
||||||
const DiagnosticsView({super.key});
|
const DiagnosticsView({super.key});
|
||||||
|
|
@ -98,8 +98,11 @@ class _LogViewerWidgetState extends State<LogViewerWidget> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_entries =
|
_entries = widget.logLines
|
||||||
widget.logLines.split('\n').reversed.map(_LogEntry.parse).toList();
|
.split('\n')
|
||||||
|
.reversed
|
||||||
|
.map(_LogEntry.parse)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setFilter(String level) => setState(() => _filterLevel = level);
|
void _setFilter(String level) => setState(() => _filterLevel = level);
|
||||||
|
|
@ -187,8 +190,9 @@ class _LogViewerWidgetState extends State<LogViewerWidget> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip:
|
tooltip: _showTimestamps
|
||||||
_showTimestamps ? 'Hide timestamps' : 'Show timestamps',
|
? 'Hide timestamps'
|
||||||
|
: 'Show timestamps',
|
||||||
onPressed: _toggleTimestamps,
|
onPressed: _toggleTimestamps,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_showTimestamps
|
_showTimestamps
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue