mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
fix #101 and show when not connected
This commit is contained in:
parent
b6a4cca884
commit
8763313c41
10 changed files with 185 additions and 171 deletions
|
|
@ -4,6 +4,7 @@ import 'package:twonly/globals.dart';
|
|||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/providers/api_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/providers/connection_provider.dart';
|
||||
import 'package:twonly/src/providers/hive.dart';
|
||||
import 'package:twonly/src/providers/settings_change_provider.dart';
|
||||
import 'package:twonly/src/services/fcm_service.dart';
|
||||
|
|
@ -34,6 +35,7 @@ void main() async {
|
|||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => settingsController),
|
||||
ChangeNotifierProvider(create: (_) => ConnectionChangeProvider()),
|
||||
],
|
||||
child: MyApp(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/components/connection_state.dart';
|
||||
import 'package:twonly/src/providers/connection_provider.dart';
|
||||
import 'package:twonly/src/providers/settings_change_provider.dart';
|
||||
import 'package:twonly/src/services/notification_service.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
|
@ -31,7 +31,6 @@ class MyApp extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
bool _isConnected = false;
|
||||
bool wasPaused = false;
|
||||
|
||||
@override
|
||||
|
|
@ -41,60 +40,18 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// register global callbacks to the widget tree
|
||||
globalCallbackConnectionState = (isConnected) {
|
||||
setState(() {
|
||||
_isConnected = isConnected;
|
||||
});
|
||||
globalCallbackConnectionState = (update) {
|
||||
context.read<ConnectionChangeProvider>().updateConnectionState(update);
|
||||
setupNotificationWithUsers();
|
||||
};
|
||||
|
||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// _requestPermissions();
|
||||
// _initService();
|
||||
// });
|
||||
initAsync();
|
||||
}
|
||||
|
||||
Future initAsync() async {
|
||||
// make sure the front end service will be killed
|
||||
// FlutterForegroundTask.sendDataToTask("");
|
||||
// await FlutterForegroundTask.stopService();
|
||||
// connect async to the backend api
|
||||
apiProvider.connect();
|
||||
}
|
||||
|
||||
// Future<void> _requestPermissions() async {
|
||||
// // Android 13+, you need to allow notification permission to display foreground service notification.
|
||||
// //
|
||||
// // iOS: If you need notification, ask for permission.
|
||||
// final NotificationPermission notificationPermission =
|
||||
// await FlutterForegroundTask.checkNotificationPermission();
|
||||
// if (notificationPermission != NotificationPermission.granted) {
|
||||
// await FlutterForegroundTask.requestNotificationPermission();
|
||||
// }
|
||||
|
||||
// if (Platform.isAndroid) {
|
||||
// // Android 12+, there are restrictions on starting a foreground service.
|
||||
// //
|
||||
// // To restart the service on device reboot or unexpected problem, you need to allow below permission.
|
||||
// if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) {
|
||||
// // This function requires `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission.
|
||||
// await FlutterForegroundTask.requestIgnoreBatteryOptimization();
|
||||
// }
|
||||
|
||||
// // Use this utility only if you provide services that require long-term survival,
|
||||
// // such as exact alarm service, healthcare service, or Bluetooth communication.
|
||||
// //
|
||||
// // This utility requires the "android.permission.SCHEDULE_EXACT_ALARM" permission.
|
||||
// // Using this permission may make app distribution difficult due to Google policy.
|
||||
// // if (!await FlutterForegroundTask.canScheduleExactAlarms) {
|
||||
// // When you call this function, will be gone to the settings page.
|
||||
// // So you need to explain to the user why set it.
|
||||
// // await FlutterForegroundTask.openAlarmsAndRemindersSettings();
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
|
|
@ -108,11 +65,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||
} else if (state == AppLifecycleState.paused) {
|
||||
wasPaused = true;
|
||||
globalIsAppInBackground = true;
|
||||
|
||||
// apiProvider.close(() {
|
||||
// use this only when uploading an image
|
||||
// _startService();
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,11 +117,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||
themeMode: context.watch<SettingsChangeProvider>().themeMode,
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
"/": (context) =>
|
||||
MyAppMainWidget(isConnected: _isConnected, initialPage: 0),
|
||||
"/chats": (context) =>
|
||||
MyAppMainWidget(isConnected: _isConnected, initialPage: 1)
|
||||
// home: MyAppMainWidget(isConnected: _isConnected, initialPage: 0),
|
||||
"/": (context) => MyAppMainWidget(initialPage: 0),
|
||||
"/chats": (context) => MyAppMainWidget(initialPage: 1)
|
||||
// home: MyAppMainWidget(isConnected: isConnected, initialPage: 0),
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
@ -178,10 +128,8 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||
}
|
||||
|
||||
class MyAppMainWidget extends StatefulWidget {
|
||||
const MyAppMainWidget(
|
||||
{super.key, required this.isConnected, required this.initialPage});
|
||||
const MyAppMainWidget({super.key, required this.initialPage});
|
||||
|
||||
final bool isConnected;
|
||||
final int initialPage;
|
||||
|
||||
@override
|
||||
|
|
@ -201,7 +149,9 @@ class _MyAppMainWidgetState extends State<MyAppMainWidget> {
|
|||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
if (snapshot.data!) {
|
||||
return HomeView(initialPage: widget.initialPage);
|
||||
return HomeView(
|
||||
initialPage: widget.initialPage,
|
||||
);
|
||||
}
|
||||
|
||||
if (_showOnboarding) {
|
||||
|
|
@ -226,7 +176,6 @@ class _MyAppMainWidgetState extends State<MyAppMainWidget> {
|
|||
}
|
||||
},
|
||||
),
|
||||
if (!widget.isConnected) ConnectionInfo()
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class ConnectionInfo extends StatefulWidget {
|
||||
const ConnectionInfo({super.key});
|
||||
|
|
@ -9,69 +8,87 @@ class ConnectionInfo extends StatefulWidget {
|
|||
State<ConnectionInfo> createState() => _ConnectionInfoWidgetState();
|
||||
}
|
||||
|
||||
class _ConnectionInfoWidgetState extends State<ConnectionInfo> {
|
||||
int redColorOpacity = 100; // Initial opacity value
|
||||
bool redColorGoUp = true; // Direction of the opacity change
|
||||
double screenWidth = 0; // To hold the screen width
|
||||
class _ConnectionInfoWidgetState extends State<ConnectionInfo>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _positionAnim;
|
||||
late Animation<double> _widthAnim;
|
||||
|
||||
Timer? _colorAnimationTimer;
|
||||
bool showAnimation = false;
|
||||
|
||||
final double minBarWidth = 40;
|
||||
final double maxBarWidth = 150;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startColorAnimation();
|
||||
}
|
||||
|
||||
void _startColorAnimation() {
|
||||
// Change the color every 200 milliseconds
|
||||
_colorAnimationTimer = Timer.periodic(Duration(milliseconds: 200), (timer) {
|
||||
_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) {
|
||||
_controller.repeat(reverse: true);
|
||||
setState(() {
|
||||
if (redColorOpacity <= 100) {
|
||||
redColorGoUp = true;
|
||||
}
|
||||
if (redColorOpacity >= 150) {
|
||||
redColorGoUp = false;
|
||||
}
|
||||
if (redColorGoUp) {
|
||||
redColorOpacity += 10;
|
||||
} else {
|
||||
redColorOpacity -= 10;
|
||||
}
|
||||
showAnimation = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_colorAnimationTimer?.cancel();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
screenWidth = MediaQuery.of(context).size.width; // Get the screen width
|
||||
if (!showAnimation) return Container();
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return SizedBox(
|
||||
width: screenWidth,
|
||||
height: 1,
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
double barWidth = _widthAnim.value;
|
||||
double left = _positionAnim.value * (screenWidth - barWidth);
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 3, // Position it at the top
|
||||
left: (screenWidth * 0.5) / 2, // Center it horizontally
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 100),
|
||||
width: screenWidth * 0.5, // 50% of the screen width
|
||||
left: left,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: barWidth,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.red[600]!
|
||||
.withAlpha(redColorOpacity), // Use the animated opacity
|
||||
width: 2.0, // Red border width
|
||||
),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10.0),
|
||||
), // Rounded corners
|
||||
color: context.color.primary,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,8 +238,6 @@ class ImageUploader {
|
|||
);
|
||||
|
||||
if (wasSend.isError) {
|
||||
// await box.put("retransmit-$messageId-offset", 0);
|
||||
// await box.delete("retransmit-$messageId-uploadtoken");
|
||||
Logger("api.dart").shout("error while uploading media");
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,13 +141,13 @@ class ApiProvider {
|
|||
isAuthenticated = false;
|
||||
}
|
||||
|
||||
void _onData(dynamic msgBuffer) {
|
||||
void _onData(dynamic msgBuffer) async {
|
||||
try {
|
||||
final msg = server.ServerToClient.fromBuffer(msgBuffer);
|
||||
if (msg.v0.hasResponse()) {
|
||||
messagesV0[msg.v0.seq] = msg;
|
||||
} else {
|
||||
handleServerMessage(msg);
|
||||
await handleServerMessage(msg);
|
||||
}
|
||||
} catch (e) {
|
||||
log.shout("Error parsing the servers message: $e");
|
||||
|
|
|
|||
10
lib/src/providers/connection_provider.dart
Normal file
10
lib/src/providers/connection_provider.dart
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class ConnectionChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
bool _isConnected = false;
|
||||
bool get isConnected => _isConnected;
|
||||
Future<void> updateConnectionState(bool update) async {
|
||||
_isConnected = update;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||
extension ShortCutsExtension on BuildContext {
|
||||
AppLocalizations get lang => AppLocalizations.of(this)!;
|
||||
TwonlyDatabase get db => Provider.of<TwonlyDatabase>(this);
|
||||
ColorScheme get color => Theme.of(this).colorScheme;
|
||||
}
|
||||
|
||||
Future<void> writeLogToFile(LogRecord record) async {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/components/connection_state.dart';
|
||||
import 'package:twonly/src/components/flame.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||
|
|
@ -12,6 +14,7 @@ import 'package:twonly/src/database/twonly_database.dart';
|
|||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/json_models/message.dart';
|
||||
import 'package:twonly/src/providers/api/media.dart';
|
||||
import 'package:twonly/src/providers/connection_provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
|
||||
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
|
||||
|
|
@ -31,6 +34,7 @@ class ChatListView extends StatefulWidget {
|
|||
class _ChatListViewState extends State<ChatListView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isConnected = context.watch<ConnectionChangeProvider>().isConnected;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("twonly"),
|
||||
|
|
@ -71,7 +75,16 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
)
|
||||
],
|
||||
),
|
||||
body: StreamBuilder(
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: isConnected ? Container() : ConnectionInfo(),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: StreamBuilder(
|
||||
stream: twonlyDatabase.contactsDao.watchContactsForChatList(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data == null) {
|
||||
|
|
@ -93,7 +106,8 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
),
|
||||
);
|
||||
},
|
||||
label: Text(context.lang.chatListViewSearchUserNameBtn)),
|
||||
label:
|
||||
Text(context.lang.chatListViewSearchUserNameBtn)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -126,6 +140,9 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30.0),
|
||||
child: FloatingActionButton(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
|
|
@ -34,6 +35,8 @@ class _StartNewChat extends State<StartNewChat> {
|
|||
twonlyDatabase.contactsDao.watchContactsForShareView();
|
||||
|
||||
contactSub = stream.listen((update) {
|
||||
update.sort((a, b) =>
|
||||
getContactDisplayName(a).compareTo(getContactDisplayName(b)));
|
||||
setState(() {
|
||||
allContacts = update;
|
||||
});
|
||||
|
|
@ -83,6 +86,7 @@ class _StartNewChat extends State<StartNewChat> {
|
|||
onChanged: (_) {
|
||||
filterUsers();
|
||||
},
|
||||
controller: searchUserName,
|
||||
decoration: getInputDecoration(
|
||||
context,
|
||||
context.lang.shareImageSearchAllContacts,
|
||||
|
|
@ -116,21 +120,18 @@ class UserList extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Step 1: Sort the users alphabetically
|
||||
users
|
||||
.sort((a, b) => b.lastMessageExchange.compareTo(a.lastMessageExchange));
|
||||
|
||||
return ListView.builder(
|
||||
restorationId: 'new_message_users_list',
|
||||
itemCount: users.length + 2,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
if (i == 0) {
|
||||
return ListTile(
|
||||
key: Key("add_new_contact"),
|
||||
title: Text(context.lang.startNewChatNewContact),
|
||||
leading: CircleAvatar(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.userPlus,
|
||||
size: 15,
|
||||
size: 13,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
|
|
@ -144,17 +145,17 @@ class UserList extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
if (i == 1) {
|
||||
return HeadLineComponent(context.lang.startNewChatYourContacts);
|
||||
return Divider();
|
||||
}
|
||||
Contact user = users[i - 2];
|
||||
int flameCounter = getFlameCounterFromContact(user);
|
||||
return UserContextMenu(
|
||||
key: Key(user.userId.toString()),
|
||||
contact: user,
|
||||
child: ListTile(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start, // Center horizontally
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center, // Center vertically
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(getContactDisplayName(user)),
|
||||
if (flameCounter >= 1)
|
||||
|
|
@ -164,9 +165,25 @@ class UserList extends StatelessWidget {
|
|||
maxTotalMediaCounter,
|
||||
prefix: true,
|
||||
),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.boxOpen,
|
||||
size: 13,
|
||||
color: user.archived ? null : Colors.transparent),
|
||||
onPressed: user.archived
|
||||
? () async {
|
||||
final update =
|
||||
ContactsCompanion(archived: Value(false));
|
||||
await twonlyDatabase.contactsDao
|
||||
.updateContact(user.userId, update);
|
||||
}
|
||||
: null)
|
||||
],
|
||||
),
|
||||
leading: ContactAvatar(contact: user),
|
||||
leading: ContactAvatar(
|
||||
contact: user,
|
||||
fontSize: 13,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ import 'package:flutter/material.dart';
|
|||
Function(int) globalUpdateOfHomeViewPageIndex = (a) {};
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
const HomeView({super.key, required this.initialPage});
|
||||
const HomeView({
|
||||
super.key,
|
||||
required this.initialPage,
|
||||
});
|
||||
final int initialPage;
|
||||
|
||||
@override
|
||||
|
|
|
|||
Loading…
Reference in a new issue