diff --git a/assets/core/Tor_icon.png b/assets/core/Tor_icon.png new file mode 100644 index 0000000..0c03273 Binary files /dev/null and b/assets/core/Tor_icon.png differ diff --git a/assets/core/Tor_icon.svg b/assets/core/Tor_icon.svg new file mode 100644 index 0000000..605a895 --- /dev/null +++ b/assets/core/Tor_icon.svg @@ -0,0 +1,58 @@ + + + +image/svg+xml + + + \ No newline at end of file diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index f0f992a..53aa064 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -9,6 +9,9 @@ abstract class Cwtch { // ignore: non_constant_identifier_names void LoadProfiles(String pass); + // ignore: non_constant_identifier_names + void ResetTor(); + // todo: remove these // ignore: non_constant_identifier_names void SendProfileEvent(String onion, String jsonEvent); diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 30cdde6..449dba1 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:developer'; +import 'package:flutter_app/torstatus.dart'; + import '../errorHandler.dart'; import '../model.dart'; import '../settings.dart'; @@ -11,11 +13,13 @@ class CwtchNotifier { ProfileListState profileCN; Settings settings; ErrorHandler error; + TorStatus torStatus; - CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN) { + CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN) { profileCN = pcn; settings = settingsCN; error = errorCN; + torStatus = torStatusCN; } void handleMessage(String type, dynamic data) { @@ -75,6 +79,7 @@ class CwtchNotifier { break; case "ACNStatus": print("acn status: $data"); + torStatus.handleUpdate(int.parse(data["Progress"]), data["Status"]); break; default: print("unhandled event: $type"); diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index b1c2699..b23e29c 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -297,4 +297,11 @@ class CwtchFfi implements Cwtch { final u3 = message.toNativeUtf8(); SendMessage(u1, u1.length, u2, u2.length, u3, u3.length); } + + @override + void ResetTor() { + var resetTor = library.lookup>("c_ResetTor"); + final ResetTor = resetTor.asFunction(); + ResetTor(); + } } diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index f168d61..52aa807 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -142,4 +142,10 @@ class CwtchGomobile implements Cwtch { void SendMessage(String profileOnion, String contactHandle, String message) { cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message}); } + + @override + // ignore: non_constant_identifier_names + void ResetTor() { + cwtchPlatform.invokeMethod("ResetTor", {}); + } } diff --git a/lib/main.dart b/lib/main.dart index 3a259ed..ce8e71c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter_app/cwtch/gomobile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app/errorHandler.dart'; import 'package:flutter_app/settings.dart'; +import 'package:flutter_app/torstatus.dart'; import 'package:flutter_app/views/triplecolview.dart'; import 'package:provider/provider.dart'; import 'cwtch/cwtch.dart'; @@ -18,6 +19,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; var globalSettings = Settings(Locale("en", ''), Opaque.dark); var globalErrorHandler = ErrorHandler(); +var globalTorStatus = TorStatus(); void main() { LicenseRegistry.addLicense(() => licenses()); @@ -48,7 +50,7 @@ class FlwtchState extends State { cwtchInit = false; profs = ProfileListState(); - var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler); + var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus); if (Platform.isAndroid) { cwtch = CwtchGomobile(cwtchNotifier); @@ -65,6 +67,7 @@ class FlwtchState extends State { appStatus = AppModel(cwtch: cwtch); } + ChangeNotifierProvider getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus); ChangeNotifierProvider getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler); ChangeNotifierProvider getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings); Provider getFlwtchStateProvider() => Provider(create: (_) => this); @@ -75,7 +78,7 @@ class FlwtchState extends State { //appStatus = AppModel(cwtch: cwtch); return MultiProvider( - providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider()], + providers: [getFlwtchStateProvider(), getProfileListProvider(), getSettingsProvider(), getErrorHandlerProvider(), getTorStatusProvider()], builder: (context, widget) { Provider.of(context).initPackageInfo(); return Consumer( diff --git a/lib/torstatus.dart b/lib/torstatus.dart new file mode 100644 index 0000000..076d3e1 --- /dev/null +++ b/lib/torstatus.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class TorStatus extends ChangeNotifier { + int progress; + String status; + bool connected; + + /// Called by the event bus. + handleUpdate(int new_progress, String new_status) { + if (progress == 100) { + connected = true; + } else { + connected = false; + } + + progress = new_progress; + status = new_status; + + notifyListeners(); + } +} diff --git a/lib/views/profilemgrview.dart b/lib/views/profilemgrview.dart index e7015e5..53631b6 100644 --- a/lib/views/profilemgrview.dart +++ b/lib/views/profilemgrview.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_app/settings.dart'; +import 'package:flutter_app/views/torstatusview.dart'; import 'package:flutter_app/widgets/passwordfield.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_app/widgets/profilerow.dart'; @@ -34,6 +35,13 @@ class _ProfileMgrViewState extends State { appBar: AppBar( title: Text(AppLocalizations.of(context).titleManageProfiles), actions: [ + IconButton( + icon: Image( + image: AssetImage("assets/core/Tor_icon.png"), + filterQuality: FilterQuality.low, + isAntiAlias: false, + ), + onPressed: _pushTorStatus), IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug), IconButton( icon: Icon(Icons.lock_open), @@ -75,6 +83,17 @@ class _ProfileMgrViewState extends State { )); } + void _pushTorStatus() { + Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) { + return Provider( + create: (_) => Provider.of(context, listen: false), + child: TorStatusView(), + ); + }, + )); + } + void _pushAddEditProfile({onion: ""}) { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) { diff --git a/lib/views/torstatusview.dart b/lib/views/torstatusview.dart new file mode 100644 index 0000000..bef0767 --- /dev/null +++ b/lib/views/torstatusview.dart @@ -0,0 +1,63 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_app/settings.dart'; +import 'package:flutter_app/torstatus.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../main.dart'; + +/// Tor Status View provides all info on Tor network state and the (future) ability to configure the network in a variety +/// of ways (restart, enable bridges, enable pluggable transports etc) +class TorStatusView extends StatefulWidget { + @override + _TorStatusView createState() => _TorStatusView(); +} + +class _TorStatusView extends State { + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Tor Network Status"), + ), + body: _buildSettingsList(), + ); + } + + Widget _buildSettingsList() { + return Consumer(builder: (context, torStatus, child) { + return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { + return Scrollbar( + isAlwaysShown: true, + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column(children: [ + ListTile( + leading: Image( + image: AssetImage("assets/core/Tor_icon.png"), + ), + title: Text("Tor Status"), + subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context).networkStatusOnline : torStatus.status), + trailing: ElevatedButton( + child: Text("Reset"), + onPressed: () { + Provider.of(context, listen: false).cwtch.ResetTor(); + }, + ), + ) + ])))); + }); + }); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 3343752..c70e4d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,6 +83,7 @@ flutter: assets: - assets/ + - assets/core/ - assets/profiles/ # To add custom fonts to your application, add a fonts section here, diff --git a/test/profileimage_init.png b/test/profileimage_init.png new file mode 100644 index 0000000..a7f5369 Binary files /dev/null and b/test/profileimage_init.png differ diff --git a/test/profileimage_test.dart b/test/profileimage_test.dart new file mode 100644 index 0000000..bea67be --- /dev/null +++ b/test/profileimage_test.dart @@ -0,0 +1,72 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/opaque.dart'; +import 'package:flutter_app/settings.dart'; +import 'package:flutter_app/widgets/cwtchlabel.dart'; +import 'package:flutter_app/widgets/profileimage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +var settingsEnglishDark = Settings(Locale("en", ''), Opaque.dark); +var settingsEnglishLight = Settings(Locale("en", ''), Opaque.light); +ChangeNotifierProvider getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark); + +String file(String slug) { + return "profileimage_" + slug + ".png"; +} + +void main() { + + testWidgets('ProfileImage widget test', (WidgetTester tester) async { + tester.binding.window.physicalSizeTestValue = Size(200, 200); + // await tester.pumpWidget(MultiProvider( + // providers:[getSettingsEnglishDark()], + // child: Directionality(textDirection: TextDirection.ltr, child: CwtchLabel(label: testingStr)) + // )); + + Widget testWidget = ProfileImage( + imagePath: "profiles/001-centaur.png", + badgeTextColor: settingsEnglishDark.theme.portraitProfileBadgeTextColor(), + badgeColor: settingsEnglishDark.theme.portraitProfileBadgeColor(), + maskOut: false, + border: settingsEnglishDark.theme.portraitOfflineBorderColor(), + diameter: 64.0, + badgeCount: 10, + ); + + Widget testHarness = MultiProvider( + providers:[getSettingsEnglishDark()], + builder: (context, child) { return MaterialApp( + locale: Provider.of(context).locale, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + title: 'Test', + theme: mkThemeData(Provider.of(context)), + home: Card(child: testWidget), + );} + ); + + // Verify that our counter starts at 0. + //expect(find.text(testingStr), findsOneWidget); + //expect(find.text('1'), findsNothing); + + await tester.pumpWidget(testHarness); + await expectLater(find.byWidget(testHarness), matchesGoldenFile(file('init'))); + + // Tap the '+' icon and trigger a frame. + // await tester.tap(find.byIcon(Icons.add)); + // await tester.pump(); + // + // // Verify that our counter has incremented. + // expect(find.text('0'), findsNothing); + // expect(find.text('1'), findsOneWidget); + }); +}