refactor mobile tor settings

This commit is contained in:
julian 2023-09-13 09:49:12 -06:00
parent 8068534e52
commit 27a7861bad

View file

@ -8,19 +8,23 @@
* *
*/ */
import 'dart:async';
import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/tor_service.dart'; import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart';
@ -37,122 +41,8 @@ class TorSettingsView extends ConsumerStatefulWidget {
} }
class _TorSettingsViewState extends ConsumerState<TorSettingsView> { class _TorSettingsViewState extends ConsumerState<TorSettingsView> {
late TorConnectionStatus _networkStatus;
Widget _buildTorIcon(TorConnectionStatus status) {
switch (status) {
case TorConnectionStatus.disconnected:
return GestureDetector(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
SvgPicture.asset(
Assets.svg.tor,
color:
Theme.of(context).extension<StackColors>()!.textSubtitle3,
width: 200,
height: 200,
),
Text(
"CONNECT",
style: STextStyles.smallMed14(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.popupBG),
)
],
),
onTap: () async {
await connect();
});
case TorConnectionStatus.connected:
return GestureDetector(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
SvgPicture.asset(
Assets.svg.tor,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen,
width: 200,
height: 200,
),
Text(
"STOP",
style: STextStyles.smallMed14(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.popupBG),
)
],
),
onTap: () async {
// TODO we could make this sync.
await disconnect(); // TODO we could do away with the Future here.
});
case TorConnectionStatus.connecting:
return Stack(
alignment: AlignmentDirectional.center,
children: [
SvgPicture.asset(
Assets.svg.tor,
color:
Theme.of(context).extension<StackColors>()!.accentColorYellow,
width: 200,
height: 200,
),
Text(
"CONNECTING",
style: STextStyles.smallMed14(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.popupBG),
)
],
);
}
}
Widget _buildTorStatus(TorConnectionStatus status) {
switch (status) {
case TorConnectionStatus.disconnected:
return Text(
"Disconnected",
style: STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textSubtitle3),
);
case TorConnectionStatus.connected:
return Text(
"Connected",
style: STextStyles.itemSubtitle(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.accentColorGreen),
);
case TorConnectionStatus.connecting:
return Text(
"Connecting",
style: STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorYellow),
);
}
}
@override
void initState() {
_networkStatus = ref.read(pTorService).enabled
? TorConnectionStatus.connected
: TorConnectionStatus.disconnected;
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
return Background( return Background(
child: Scaffold( child: Scaffold(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@ -204,79 +94,19 @@ class _TorSettingsViewState extends ConsumerState<TorSettingsView> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row( const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
child: _buildTorIcon(_networkStatus), child: TorIcon(),
), ),
], ],
), ),
const SizedBox( const SizedBox(
height: 30, height: 30,
), ),
GestureDetector( const TorButton(),
child: RoundedWhiteContainer(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Text(
"Tor status",
style: STextStyles.titleBold12(context),
),
const Spacer(),
_buildTorStatus(_networkStatus),
],
),
),
),
onTap: () async {
// Connect or disconnect when the user taps the status.
switch (_networkStatus) {
case TorConnectionStatus.disconnected:
// Update the UI.
setState(() {
_networkStatus = TorConnectionStatus.connecting;
});
try {
await connect();
} catch (e, s) {
Logging.instance.log(
"Error starting tor: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
// Update the UI.
setState(() {
_networkStatus = TorConnectionStatus.connected;
});
break;
case TorConnectionStatus.connected:
try {
await disconnect();
} catch (e, s) {
Logging.instance.log(
"Error stopping tor: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
// Update the UI.
setState(() {
_networkStatus = TorConnectionStatus.disconnected;
});
break;
case TorConnectionStatus.connecting:
// Do nothing.
break;
}
}),
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
@ -363,6 +193,315 @@ class _TorSettingsViewState extends ConsumerState<TorSettingsView> {
), ),
); );
} }
}
class TorIcon extends ConsumerStatefulWidget {
const TorIcon({super.key});
@override
ConsumerState<TorIcon> createState() => _TorIconState();
}
class _TorIconState extends ConsumerState<TorIcon> {
late TorConnectionStatus _status;
Color _color(
TorConnectionStatus status,
StackColors colors,
) {
switch (status) {
case TorConnectionStatus.disconnected:
return colors.textSubtitle3;
case TorConnectionStatus.connected:
return colors.accentColorGreen;
case TorConnectionStatus.connecting:
return colors.accentColorYellow;
}
}
String _label(
TorConnectionStatus status,
StackColors colors,
) {
switch (status) {
case TorConnectionStatus.disconnected:
return "CONNECT";
case TorConnectionStatus.connected:
return "STOP";
case TorConnectionStatus.connecting:
return "CONNECTING";
}
}
bool _tapLock = false;
Future<void> onTap() async {
if (_tapLock) {
return;
}
_tapLock = true;
try {
// Connect or disconnect when the user taps the status.
switch (_status) {
case TorConnectionStatus.disconnected:
await _connectTor(ref, context);
break;
case TorConnectionStatus.connected:
await _disconnectTor(ref, context);
break;
case TorConnectionStatus.connecting:
// Do nothing.
break;
}
} catch (_) {
// any exceptions should already be handled with error dialogs
// this try catch is just extra protection to ensure _tapLock gets reset
// in the finally block in the event of an unknown error
} finally {
_tapLock = false;
}
}
@override
void initState() {
_status = ref.read(pTorService).enabled
? TorConnectionStatus.connected
: TorConnectionStatus.disconnected;
super.initState();
}
@override
Widget build(BuildContext context) {
return _TorSubscriptionBase(
onTorStatusChanged: (status) {
setState(() {
_status = status;
});
},
child: ConditionalParent(
condition: _status != TorConnectionStatus.connecting,
builder: (child) => GestureDetector(
onTap: onTap,
child: child,
),
child: SizedBox(
width: 220,
height: 220,
child: Stack(
alignment: AlignmentDirectional.center,
children: [
SvgPicture.asset(
Assets.svg.tor,
color: _color(
_status,
Theme.of(context).extension<StackColors>()!,
),
width: 200,
height: 200,
),
Text(
_label(
_status,
Theme.of(context).extension<StackColors>()!,
),
style: STextStyles.smallMed14(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.popupBG,
),
),
],
),
),
),
);
}
}
class TorButton extends ConsumerStatefulWidget {
const TorButton({super.key});
@override
ConsumerState<TorButton> createState() => _TorButtonState();
}
class _TorButtonState extends ConsumerState<TorButton> {
late TorConnectionStatus _status;
Color _color(
TorConnectionStatus status,
StackColors colors,
) {
switch (status) {
case TorConnectionStatus.disconnected:
return colors.textSubtitle3;
case TorConnectionStatus.connected:
return colors.accentColorGreen;
case TorConnectionStatus.connecting:
return colors.accentColorYellow;
}
}
String _label(
TorConnectionStatus status,
StackColors colors,
) {
switch (status) {
case TorConnectionStatus.disconnected:
return "Disconnected";
case TorConnectionStatus.connected:
return "Connected";
case TorConnectionStatus.connecting:
return "Connecting";
}
}
bool _tapLock = false;
Future<void> onTap() async {
if (_tapLock) {
return;
}
_tapLock = true;
try {
// Connect or disconnect when the user taps the status.
switch (_status) {
case TorConnectionStatus.disconnected:
await _connectTor(ref, context);
break;
case TorConnectionStatus.connected:
await _disconnectTor(ref, context);
break;
case TorConnectionStatus.connecting:
// Do nothing.
break;
}
} catch (_) {
// any exceptions should already be handled with error dialogs
// this try catch is just extra protection to ensure _tapLock gets reset
// in the finally block in the event of an unknown error
} finally {
_tapLock = false;
}
}
@override
void initState() {
_status = ref.read(pTorService).enabled
? TorConnectionStatus.connected
: TorConnectionStatus.disconnected;
super.initState();
}
@override
Widget build(BuildContext context) {
return _TorSubscriptionBase(
onTorStatusChanged: (status) {
setState(() {
_status = status;
});
},
child: GestureDetector(
onTap: onTap,
child: RoundedWhiteContainer(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Text(
"Tor status",
style: STextStyles.titleBold12(context),
),
const Spacer(),
Text(
_label(
_status,
Theme.of(context).extension<StackColors>()!,
),
style: STextStyles.itemSubtitle(context).copyWith(
color: _color(
_status,
Theme.of(context).extension<StackColors>()!,
),
),
),
],
),
),
),
),
);
}
}
class _TorSubscriptionBase extends ConsumerStatefulWidget {
const _TorSubscriptionBase({
super.key,
required this.onTorStatusChanged,
this.eventBus,
required this.child,
});
final Widget child;
final void Function(TorConnectionStatus) onTorStatusChanged;
final EventBus? eventBus;
@override
ConsumerState<_TorSubscriptionBase> createState() =>
_TorSubscriptionBaseState();
}
class _TorSubscriptionBaseState extends ConsumerState<_TorSubscriptionBase> {
/// The global event bus.
late final EventBus eventBus;
/// Subscription to the TorConnectionStatusChangedEvent.
late StreamSubscription<TorConnectionStatusChangedEvent>
_torConnectionStatusSubscription;
@override
void initState() {
// Initialize the global event bus.
eventBus = widget.eventBus ?? GlobalEventBus.instance;
// Subscribe to the TorConnectionStatusChangedEvent.
_torConnectionStatusSubscription =
eventBus.on<TorConnectionStatusChangedEvent>().listen(
(event) async {
widget.onTorStatusChanged.call(event.newStatus);
},
);
super.initState();
}
@override
void dispose() {
// Clean up the TorConnectionStatusChangedEvent subscription.
_torConnectionStatusSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
/// Connect to the Tor network. /// Connect to the Tor network.
/// ///
@ -371,15 +510,10 @@ class _TorSettingsViewState extends ConsumerState<TorSettingsView> {
/// Throws an exception if the Tor service fails to start. /// Throws an exception if the Tor service fails to start.
/// ///
/// Returns a Future that completes when the Tor service has started. /// Returns a Future that completes when the Tor service has started.
Future<void> connect() async { Future<void> _connectTor(WidgetRef ref, BuildContext context) async {
// Init the Tor service if it hasn't already been. // Init the Tor service if it hasn't already been.
ref.read(pTorService).init(); ref.read(pTorService).init();
// Update the UI.
setState(() {
_networkStatus = TorConnectionStatus.connecting;
});
// Start the Tor service. // Start the Tor service.
try { try {
await ref.read(pTorService).start(); await ref.read(pTorService).start();
@ -391,6 +525,7 @@ class _TorSettingsViewState extends ConsumerState<TorSettingsView> {
"Error starting tor: $e\n$s", "Error starting tor: $e\n$s",
level: LogLevel.Error, level: LogLevel.Error,
); );
// TODO: show dialog with error message
} }
return; return;
@ -403,7 +538,7 @@ class _TorSettingsViewState extends ConsumerState<TorSettingsView> {
/// Throws an exception if the Tor service fails to stop. /// Throws an exception if the Tor service fails to stop.
/// ///
/// Returns a Future that completes when the Tor service has stopped. /// Returns a Future that completes when the Tor service has stopped.
Future<void> disconnect() async { Future<void> _disconnectTor(WidgetRef ref, BuildContext context) async {
// Stop the Tor service. // Stop the Tor service.
try { try {
await ref.read(pTorService).stop(); await ref.read(pTorService).stop();
@ -415,12 +550,6 @@ class _TorSettingsViewState extends ConsumerState<TorSettingsView> {
"Error stopping tor: $e\n$s", "Error stopping tor: $e\n$s",
level: LogLevel.Error, level: LogLevel.Error,
); );
} // TODO: show dialog with error message
setState(() {
_networkStatus = TorConnectionStatus.disconnected;
});
return;
} }
} }