Formatting + Read/Write Settings

This commit is contained in:
Sarah Jamie Lewis 2021-03-10 09:40:14 -08:00
parent ce53fc68f2
commit 4190630cd9
27 changed files with 2265 additions and 995 deletions

View File

@ -132,6 +132,10 @@ class MainActivity: FlutterActivity() {
val jsonEvent = (call.argument("jsonEvent") as? String) ?: ""; val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
Cwtch.sendProfileEvent(onion, jsonEvent); Cwtch.sendProfileEvent(onion, jsonEvent);
} }
"SendAppEvent" -> {
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
Cwtch.sendAppEvent(jsonEvent);
}
else -> result.notImplemented() else -> result.notImplemented()
} }
} }

View File

@ -5,6 +5,7 @@ abstract class Cwtch {
void CreateProfile(String nick, String pass); void CreateProfile(String nick, String pass);
void LoadProfiles(String pass); void LoadProfiles(String pass);
void SendProfileEvent(String onion, String jsonEvent); void SendProfileEvent(String onion, String jsonEvent);
void SendAppEvent(String jsonEvent);
Future<String> ACNEvents(); Future<String> ACNEvents();
Future<String> ContactEvents(); Future<String> ContactEvents();
@ -15,4 +16,4 @@ abstract class Cwtch {
Future<int> NumMessages(String profile, String handle); Future<int> NumMessages(String profile, String handle);
Future<String> GetMessage(String profile, String handle, int index); Future<String> GetMessage(String profile, String handle, int index);
Future<String> GetMessages(String profile, String handle, int start, int end); Future<String> GetMessages(String profile, String handle, int start, int end);
} }

View File

@ -1,18 +1,32 @@
import 'dart:convert';
import 'package:provider/provider.dart';
import 'package:provider/provider.dart';
import '../model.dart'; import '../model.dart';
import '../settings.dart';
// Class that handles libcwtch-go events (received either via ffi with an isolate or gomobile over a method channel from kotlin) // Class that handles libcwtch-go events (received either via ffi with an isolate or gomobile over a method channel from kotlin)
// Takes Notifiers and triggers them on appropriate events // Takes Notifiers and triggers them on appropriate events
class CwtchNotifier { class CwtchNotifier {
ProfileListState profileCN; ProfileListState profileCN;
Settings settings;
CwtchNotifier(ProfileListState pcn) { CwtchNotifier(ProfileListState pcn, Settings settingsCN) {
profileCN = pcn; profileCN = pcn;
settings = settingsCN;
} }
void handleMessage(String type, dynamic data) { void handleMessage(String type, dynamic data) {
switch (type) { switch (type) {
case "NewPeer": case "NewPeer":
profileCN.add(ProfileInfoState(onion: data["Identity"], nickname: data["name"], imagePath: data["picture"])); profileCN.add(ProfileInfoState(
onion: data["Identity"],
nickname: data["name"],
imagePath: data["picture"]));
break;
case "UpdateGlobalSettings":
settings.handleUpdate(jsonDecode(data["Data"]));
break; break;
default: default:
print("unhandled gomobile appbus event: ${type}"); print("unhandled gomobile appbus event: ${type}");

View File

@ -15,41 +15,55 @@ import '../model.dart';
/// Cwtch API /// /// Cwtch API ///
///////////////////// /////////////////////
typedef start_cwtch_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2); typedef start_cwtch_function = Void Function(
typedef StartCwtchFn = void Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen); Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
typedef StartCwtchFn = void Function(
Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32); typedef void_from_string_string_function = Void Function(
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int); Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringFn = void Function(
Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef access_cwtch_eventbus_function = Void Function(); typedef access_cwtch_eventbus_function = Void Function();
typedef NextEventFn = void Function(); typedef NextEventFn = void Function();
typedef string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length); typedef string_to_void_function = Void Function(
Pointer<Utf8> str, Int32 length);
typedef StringFn = void Function(Pointer<Utf8> dir, int); typedef StringFn = void Function(Pointer<Utf8> dir, int);
typedef string_string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2); typedef string_string_to_void_function = Void Function(
Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
typedef StringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int); typedef StringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef get_json_blob_void_function = Pointer<Utf8> Function(); typedef get_json_blob_void_function = Pointer<Utf8> Function();
typedef GetJsonBlobVoidFn = Pointer<Utf8> Function(); typedef GetJsonBlobVoidFn = Pointer<Utf8> Function();
typedef get_json_blob_string_function = Pointer<Utf8> Function(Pointer<Utf8> str, Int32 length); typedef get_json_blob_string_function = Pointer<Utf8> Function(
typedef GetJsonBlobStringFn = Pointer<Utf8> Function(Pointer<Utf8> str,int len); Pointer<Utf8> str, Int32 length);
typedef GetJsonBlobStringFn = Pointer<Utf8> Function(
Pointer<Utf8> str, int len);
//func NumMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int) (n C.int) { //func NumMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int) (n C.int) {
typedef get_int_from_str_str_function = Int32 Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32); typedef get_int_from_str_str_function = Int32 Function(
typedef GetIntFromStrStrFn = int Function(Pointer<Utf8>, int, Pointer<Utf8>, int); Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef GetIntFromStrStrFn = int Function(
Pointer<Utf8>, int, Pointer<Utf8>, int);
//func GetMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, message_index C.int) *C.char { //func GetMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, message_index C.int) *C.char {
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32); typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int); Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32);
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(
Pointer<Utf8>, int, Pointer<Utf8>, int, int);
//func GetMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, start C.int, end C.int) *C.char { //func GetMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, start C.int, end C.int) *C.char {
typedef get_json_blob_from_str_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32, Int32); typedef get_json_blob_from_str_str_int_int_function = Pointer<Utf8> Function(
typedef GetJsonBlobFromStrStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int); Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32, Int32);
typedef GetJsonBlobFromStrStrIntIntFn = Pointer<Utf8> Function(
Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
typedef acn_events_function = Pointer<Utf8> Function(); typedef acn_events_function = Pointer<Utf8> Function();
typedef ACNEventsFn = Pointer<Utf8> Function(); typedef ACNEventsFn = Pointer<Utf8> Function();
class CwtchFfi implements Cwtch { class CwtchFfi implements Cwtch {
DynamicLibrary library; DynamicLibrary library;
@ -72,17 +86,18 @@ class CwtchFfi implements Cwtch {
var cwtchDir = path.join(home, ".cwtch/dev/"); var cwtchDir = path.join(home, ".cwtch/dev/");
print("cwtchDir $cwtchDir"); print("cwtchDir $cwtchDir");
var startCwtchC = library.lookup<NativeFunction<start_cwtch_function>>("c_StartCwtch"); var startCwtchC =
library.lookup<NativeFunction<start_cwtch_function>>("c_StartCwtch");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final StartCwtch = startCwtchC.asFunction<StartCwtchFn>(); final StartCwtch = startCwtchC.asFunction<StartCwtchFn>();
final ut8CwtchDir = cwtchDir.toNativeUtf8(); final ut8CwtchDir = cwtchDir.toNativeUtf8();
StartCwtch(ut8CwtchDir,ut8CwtchDir.length, "".toNativeUtf8(), 0); StartCwtch(ut8CwtchDir, ut8CwtchDir.length, "".toNativeUtf8(), 0);
// Spawn an isolate to listen to events from libcwtch-go and then dispatch them when received on main thread to cwtchNotifier // Spawn an isolate to listen to events from libcwtch-go and then dispatch them when received on main thread to cwtchNotifier
var _receivePort = ReceivePort(); var _receivePort = ReceivePort();
cwtchIsolate = await Isolate.spawn(_checkAppbusEvents, _receivePort.sendPort); cwtchIsolate =
await Isolate.spawn(_checkAppbusEvents, _receivePort.sendPort);
_receivePort.listen((message) { _receivePort.listen((message) {
var env = jsonDecode(message); var env = jsonDecode(message);
cwtchNotifier.handleMessage(env["EventType"], env["Data"]); cwtchNotifier.handleMessage(env["EventType"], env["Data"]);
@ -108,7 +123,8 @@ class CwtchFfi implements Cwtch {
// Steam of appbus events. Call blocks in libcwtch-go GetAppbusEvent. Static so the isolate can use it // Steam of appbus events. Call blocks in libcwtch-go GetAppbusEvent. Static so the isolate can use it
static Stream<String> pollAppbusEvents() async* { static Stream<String> pollAppbusEvents() async* {
var library = DynamicLibrary.open("libCwtch.so"); var library = DynamicLibrary.open("libCwtch.so");
var getAppbusEventC = library.lookup<NativeFunction<acn_events_function>>("c_GetAppBusEvent"); var getAppbusEventC =
library.lookup<NativeFunction<acn_events_function>>("c_GetAppBusEvent");
final GetAppbusEvent = getAppbusEventC.asFunction<ACNEventsFn>(); final GetAppbusEvent = getAppbusEventC.asFunction<ACNEventsFn>();
while (true) { while (true) {
@ -120,7 +136,9 @@ class CwtchFfi implements Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SelectProfile(String onion) async { void SelectProfile(String onion) async {
var selectProfileC = library.lookup<NativeFunction<get_json_blob_string_function>>("c_SelectProfile"); var selectProfileC =
library.lookup<NativeFunction<get_json_blob_string_function>>(
"c_SelectProfile");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final SelectProfile = selectProfileC.asFunction<GetJsonBlobStringFn>(); final SelectProfile = selectProfileC.asFunction<GetJsonBlobStringFn>();
final ut8Onion = onion.toNativeUtf8(); final ut8Onion = onion.toNativeUtf8();
@ -129,7 +147,9 @@ class CwtchFfi implements Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void CreateProfile(String nick, String pass) { void CreateProfile(String nick, String pass) {
var createProfileC = library.lookup<NativeFunction<void_from_string_string_function>>("c_CreateProfile"); var createProfileC =
library.lookup<NativeFunction<void_from_string_string_function>>(
"c_CreateProfile");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final CreateProfile = createProfileC.asFunction<VoidFromStringStringFn>(); final CreateProfile = createProfileC.asFunction<VoidFromStringStringFn>();
final utf8nick = nick.toNativeUtf8(); final utf8nick = nick.toNativeUtf8();
@ -139,7 +159,8 @@ class CwtchFfi implements Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void LoadProfiles(String pass) { void LoadProfiles(String pass) {
var loadProfileC = library.lookup<NativeFunction<string_to_void_function>>("c_LoadProfiles"); var loadProfileC = library
.lookup<NativeFunction<string_to_void_function>>("c_LoadProfiles");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final LoadProfiles = loadProfileC.asFunction<StringFn>(); final LoadProfiles = loadProfileC.asFunction<StringFn>();
final ut8pass = pass.toNativeUtf8(); final ut8pass = pass.toNativeUtf8();
@ -147,8 +168,8 @@ class CwtchFfi implements Cwtch {
} }
Future<String> ACNEvents() async { Future<String> ACNEvents() async {
var acnEventsC = library.lookup<NativeFunction<acn_events_function>>( var acnEventsC =
"c_ACNEvents"); library.lookup<NativeFunction<acn_events_function>>("c_ACNEvents");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final ACNEvents = acnEventsC.asFunction<ACNEventsFn>(); final ACNEvents = acnEventsC.asFunction<ACNEventsFn>();
@ -157,10 +178,9 @@ class CwtchFfi implements Cwtch {
return event; return event;
} }
Future<String> ContactEvents() async { Future<String> ContactEvents() async {
var acnEventsC = library.lookup<NativeFunction<acn_events_function>>( var acnEventsC =
"c_ContactEvents"); library.lookup<NativeFunction<acn_events_function>>("c_ContactEvents");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final ContactEvents = acnEventsC.asFunction<ACNEventsFn>(); final ContactEvents = acnEventsC.asFunction<ACNEventsFn>();
@ -170,7 +190,8 @@ class CwtchFfi implements Cwtch {
} }
Future<String> GetProfiles() async { Future<String> GetProfiles() async {
var getProfilesC = library.lookup<NativeFunction<get_json_blob_void_function>>("c_GetProfiles"); var getProfilesC = library
.lookup<NativeFunction<get_json_blob_void_function>>("c_GetProfiles");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final GetProfiles = getProfilesC.asFunction<GetJsonBlobVoidFn>(); final GetProfiles = getProfilesC.asFunction<GetJsonBlobVoidFn>();
@ -180,7 +201,8 @@ class CwtchFfi implements Cwtch {
} }
Future<String> GetContacts(String onion) async { Future<String> GetContacts(String onion) async {
var getContactsC = library.lookup<NativeFunction<get_json_blob_string_function>>("c_GetContacts"); var getContactsC = library
.lookup<NativeFunction<get_json_blob_string_function>>("c_GetContacts");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final GetContacts = getContactsC.asFunction<GetJsonBlobStringFn>(); final GetContacts = getContactsC.asFunction<GetJsonBlobStringFn>();
final utf8onion = onion.toNativeUtf8(); final utf8onion = onion.toNativeUtf8();
@ -190,44 +212,66 @@ class CwtchFfi implements Cwtch {
} }
Future<int> NumMessages(String profile, String handle) async { Future<int> NumMessages(String profile, String handle) async {
var numMessagesC = library.lookup<NativeFunction<get_int_from_str_str_function>>("c_NumMessages"); var numMessagesC = library
.lookup<NativeFunction<get_int_from_str_str_function>>("c_NumMessages");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final NumMessages = numMessagesC.asFunction<GetIntFromStrStrFn>(); final NumMessages = numMessagesC.asFunction<GetIntFromStrStrFn>();
final utf8profile = profile.toNativeUtf8(); final utf8profile = profile.toNativeUtf8();
final utf8handle = handle.toNativeUtf8(); final utf8handle = handle.toNativeUtf8();
int num = NumMessages(utf8profile, utf8profile.length, utf8handle, utf8handle.length); int num = NumMessages(
utf8profile, utf8profile.length, utf8handle, utf8handle.length);
return num; return num;
} }
Future<String> GetMessage(String profile, String handle, int index) async { Future<String> GetMessage(String profile, String handle, int index) async {
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_str_int_function>>("c_GetMessage"); var getMessageC =
library.lookup<NativeFunction<get_json_blob_from_str_str_int_function>>(
"c_GetMessage");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final GetMessage = getMessageC.asFunction<GetJsonBlobFromStrStrIntFn>(); final GetMessage = getMessageC.asFunction<GetJsonBlobFromStrStrIntFn>();
final utf8profile = profile.toNativeUtf8(); final utf8profile = profile.toNativeUtf8();
final utf8handle = handle.toNativeUtf8(); final utf8handle = handle.toNativeUtf8();
Pointer<Utf8> jsonMessageBytes = GetMessage(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index); Pointer<Utf8> jsonMessageBytes = GetMessage(
utf8profile, utf8profile.length, utf8handle, utf8handle.length, index);
String jsonMessage = jsonMessageBytes.toDartString(); String jsonMessage = jsonMessageBytes.toDartString();
return jsonMessage; return jsonMessage;
} }
Future<String> GetMessages(String profile, String handle, int start, int end) async { Future<String> GetMessages(
var getMessagesC = library.lookup<NativeFunction<get_json_blob_from_str_str_int_int_function>>("c_GetMessages"); String profile, String handle, int start, int end) async {
var getMessagesC = library
.lookup<NativeFunction<get_json_blob_from_str_str_int_int_function>>(
"c_GetMessages");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final GetMessages = getMessagesC.asFunction<GetJsonBlobFromStrStrIntIntFn>(); final GetMessages =
getMessagesC.asFunction<GetJsonBlobFromStrStrIntIntFn>();
final utf8profile = profile.toNativeUtf8(); final utf8profile = profile.toNativeUtf8();
final utf8handle = handle.toNativeUtf8(); final utf8handle = handle.toNativeUtf8();
Pointer<Utf8> jsonMessagesBytes = GetMessages(utf8profile, utf8profile.length, utf8handle, utf8handle.length, start, end); Pointer<Utf8> jsonMessagesBytes = GetMessages(utf8profile,
utf8profile.length, utf8handle, utf8handle.length, start, end);
String jsonMessages = jsonMessagesBytes.toDartString(); String jsonMessages = jsonMessagesBytes.toDartString();
return jsonMessages; return jsonMessages;
} }
@override @override
void SendProfileEvent(String onion, String json) { void SendProfileEvent(String onion, String json) {
var sendAppBusEvent = library.lookup<NativeFunction<string_string_to_void_function>>("c_SendProfileEvent"); var sendAppBusEvent =
library.lookup<NativeFunction<string_string_to_void_function>>(
"c_SendProfileEvent");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final SendAppBusEvent = sendAppBusEvent.asFunction<StringStringFn>(); final SendAppBusEvent = sendAppBusEvent.asFunction<StringStringFn>();
final utf8onion = onion.toNativeUtf8(); final utf8onion = onion.toNativeUtf8();
final utf8json = json.toNativeUtf8(); final utf8json = json.toNativeUtf8();
SendAppBusEvent(utf8onion, utf8onion.length, utf8json, utf8json.length); SendAppBusEvent(utf8onion, utf8onion.length, utf8json, utf8json.length);
} }
}
@override
void SendAppEvent(String json) {
var sendAppBusEvent = library
.lookup<NativeFunction<string_to_void_function>>("c_SendAppEvent");
// ignore: non_constant_identifier_names
final SendAppBusEvent = sendAppBusEvent.asFunction<StringFn>();
final utf8json = json.toNativeUtf8();
SendAppBusEvent(utf8json, utf8json.length);
}
}

View File

@ -22,7 +22,8 @@ Future startCwtch() async {
*/ */
class CwtchGomobile implements Cwtch { class CwtchGomobile implements Cwtch {
static const appInfoPlatform = const MethodChannel('test.flutter.dev/applicationInfo'); static const appInfoPlatform =
const MethodChannel('test.flutter.dev/applicationInfo');
static const cwtchPlatform = const MethodChannel('cwtch'); static const cwtchPlatform = const MethodChannel('cwtch');
final appbusEventChannelName = 'test.flutter.dev/eventBus'; final appbusEventChannelName = 'test.flutter.dev/eventBus';
@ -47,7 +48,8 @@ class CwtchGomobile implements Cwtch {
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch/dev/"); var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch/dev/");
String torPath = path.join(await androidLibraryDir, "libtor.so"); String torPath = path.join(await androidLibraryDir, "libtor.so");
print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)..."); print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)...");
cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath}); cwtchPlatform
.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath});
} }
// Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier // Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier
@ -59,7 +61,7 @@ class CwtchGomobile implements Cwtch {
} }
void SelectProfile(String onion) { void SelectProfile(String onion) {
cwtchPlatform.invokeMethod("SelectProfile", {"profile" : onion}); cwtchPlatform.invokeMethod("SelectProfile", {"profile": onion});
} }
void CreateProfile(String nick, String pass) { void CreateProfile(String nick, String pass) {
@ -84,26 +86,34 @@ class CwtchGomobile implements Cwtch {
} }
Future<String> GetContacts(String onion) { Future<String> GetContacts(String onion) {
return cwtchPlatform.invokeMethod("GetContacts", {"profile" : onion}); return cwtchPlatform.invokeMethod("GetContacts", {"profile": onion});
} }
Future<int> NumMessages(String profile, String handle) { Future<int> NumMessages(String profile, String handle) {
return cwtchPlatform.invokeMethod("NumMessages", {"profile" : profile, "contact": handle}); return cwtchPlatform
.invokeMethod("NumMessages", {"profile": profile, "contact": handle});
} }
Future<String> GetMessage(String profile, String handle, int index) { Future<String> GetMessage(String profile, String handle, int index) {
print("gomobile.dart GetMessage " + index.toString()); print("gomobile.dart GetMessage " + index.toString());
return cwtchPlatform.invokeMethod("GetMessage", {"profile" : profile, "contact": handle, "index": index}); return cwtchPlatform.invokeMethod(
"GetMessage", {"profile": profile, "contact": handle, "index": index});
} }
Future<String> GetMessages(String profile, String handle, int start, int end) { Future<String> GetMessages(
return cwtchPlatform.invokeMethod("GetMessage", {"profile" : profile, "contact": handle, "start": start, "end": end}); String profile, String handle, int start, int end) {
return cwtchPlatform.invokeMethod("GetMessage",
{"profile": profile, "contact": handle, "start": start, "end": end});
} }
@override @override
void SendProfileEvent(String onion, String jsonEvent) { void SendProfileEvent(String onion, String jsonEvent) {
cwtchPlatform.invokeMethod("SendProfileEvent", {"onion" : onion, "jsonEvent": jsonEvent}); cwtchPlatform.invokeMethod(
"SendProfileEvent", {"onion": onion, "jsonEvent": jsonEvent});
} }
@override
} void SendAppEvent(String jsonEvent) {
cwtchPlatform.invokeMethod("SendAppEvent", {"jsonEvent": jsonEvent});
}
}

View File

@ -13,6 +13,8 @@ import 'dart:io' show Platform;
import 'opaque.dart'; import 'opaque.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var GlobalSettings = Settings(Locale("en", ''), Opaque.dark);
void main() => runApp(Flwtch()); void main() => runApp(Flwtch());
class Flwtch extends StatefulWidget { class Flwtch extends StatefulWidget {
@ -39,7 +41,7 @@ class FlwtchState extends State<Flwtch> {
cwtchInit = false; cwtchInit = false;
profs = ProfileListState(); profs = ProfileListState();
var cwtchNotifier = new CwtchNotifier(profs); var cwtchNotifier = new CwtchNotifier(profs, GlobalSettings);
if (Platform.isAndroid) { if (Platform.isAndroid) {
cwtch = CwtchGomobile(cwtchNotifier); cwtch = CwtchGomobile(cwtchNotifier);
@ -56,20 +58,26 @@ class FlwtchState extends State<Flwtch> {
appStatus = AppModel(cwtch: cwtch); appStatus = AppModel(cwtch: cwtch);
} }
ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider(create: (context) => Settings(Locale("en", ''))); ChangeNotifierProvider<Settings> getSettingsProvider() =>
ChangeNotifierProvider<OpaqueTheme> getOpaqueProvider() => ChangeNotifierProvider(create: (context) => OpaqueTheme(Opaque.dark)); ChangeNotifierProvider(create: (context) => GlobalSettings);
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this); Provider<FlwtchState> getFlwtchStateProvider() =>
ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs); Provider<FlwtchState>(create: (_) => this);
ChangeNotifierProvider<ProfileListState> getProfileListProvider() =>
ChangeNotifierProvider(create: (context) => profs);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
appStatus = AppModel(cwtch: cwtch); appStatus = AppModel(cwtch: cwtch);
return MultiProvider( return MultiProvider(
providers: [getFlwtchStateProvider(), getProfileListProvider(), getOpaqueProvider(), getSettingsProvider()], providers: [
getFlwtchStateProvider(),
getProfileListProvider(),
getSettingsProvider()
],
builder: (context, widget) { builder: (context, widget) {
Provider.of<Settings>(context).initPackageInfo(); Provider.of<Settings>(context).initPackageInfo();
return Consumer<OpaqueTheme>( return Consumer<Settings>(
builder: (context, opaque, child) => MaterialApp( builder: (context, opaque, child) => MaterialApp(
locale: Provider.of<Settings>(context).locale, locale: Provider.of<Settings>(context).locale,
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
@ -83,42 +91,46 @@ class FlwtchState extends State<Flwtch> {
accentColor: opaque.current().defaultButtonColor(), accentColor: opaque.current().defaultButtonColor(),
buttonColor: opaque.current().defaultButtonColor(), buttonColor: opaque.current().defaultButtonColor(),
backgroundColor: opaque.current().backgroundMainColor(), backgroundColor: opaque.current().backgroundMainColor(),
iconTheme: IconThemeData ( iconTheme: IconThemeData(
color: opaque.current().mainTextColor(), color: opaque.current().mainTextColor(),
), ),
cardColor: opaque.current().backgroundMainColor(), cardColor: opaque.current().backgroundMainColor(),
textButtonTheme: TextButtonThemeData ( textButtonTheme: TextButtonThemeData(
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()), backgroundColor: MaterialStateProperty.all(
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()), opaque.current().defaultButtonColor()),
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()), foregroundColor: MaterialStateProperty.all(
padding: MaterialStateProperty.all(EdgeInsets.all(20)) opaque.current().defaultButtonTextColor()),
), overlayColor: MaterialStateProperty.all(
), opaque.current().defaultButtonActiveColor()),
dialogTheme: DialogTheme ( padding: MaterialStateProperty.all(EdgeInsets.all(20))),
backgroundColor: opaque.current().backgroundPaneColor(),
titleTextStyle: TextStyle( color: opaque.current().mainTextColor()),
contentTextStyle: TextStyle( color: opaque.current().mainTextColor())
), ),
dialogTheme: DialogTheme(
backgroundColor: opaque.current().backgroundPaneColor(),
titleTextStyle:
TextStyle(color: opaque.current().mainTextColor()),
contentTextStyle:
TextStyle(color: opaque.current().mainTextColor())),
textTheme: TextTheme( textTheme: TextTheme(
headline1: TextStyle( color: opaque.current().mainTextColor()), headline1: TextStyle(color: opaque.current().mainTextColor()),
headline2: TextStyle( color: opaque.current().mainTextColor()), headline2: TextStyle(color: opaque.current().mainTextColor()),
headline3: TextStyle( color: opaque.current().mainTextColor()), headline3: TextStyle(color: opaque.current().mainTextColor()),
headline4: TextStyle( color: opaque.current().mainTextColor()), headline4: TextStyle(color: opaque.current().mainTextColor()),
headline5: TextStyle( color: opaque.current().mainTextColor()), headline5: TextStyle(color: opaque.current().mainTextColor()),
headline6: TextStyle( color: opaque.current().mainTextColor()), headline6: TextStyle(color: opaque.current().mainTextColor()),
bodyText1: TextStyle( color: opaque.current().mainTextColor()), bodyText1: TextStyle(color: opaque.current().mainTextColor()),
bodyText2: TextStyle( color: opaque.current().mainTextColor()), bodyText2: TextStyle(color: opaque.current().mainTextColor()),
subtitle1: TextStyle( color: opaque.current().mainTextColor()), subtitle1: TextStyle(color: opaque.current().mainTextColor()),
subtitle2: TextStyle( color: opaque.current().mainTextColor()), subtitle2: TextStyle(color: opaque.current().mainTextColor()),
caption: TextStyle( color: opaque.current().mainTextColor()), caption: TextStyle(color: opaque.current().mainTextColor()),
button: TextStyle( color: opaque.current().mainTextColor()), button: TextStyle(color: opaque.current().mainTextColor()),
overline: TextStyle( color: opaque.current().mainTextColor()) overline: TextStyle(color: opaque.current().mainTextColor())),
),
), ),
// from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(), // from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(),
// from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(), // from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(),
home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ProfileMgrView()) : SplashView(), home: cwtchInit == true
? (columns.length == 3 ? TripleColumnView() : ProfileMgrView())
: SplashView(),
), ),
); );
}, },

View File

@ -29,7 +29,13 @@ class ContactModel {
String status; String status;
String imagePath; String imagePath;
ContactModel({this.onion, this.nickname, this.status, this.isInvitation, this.isBlocked, this.imagePath}); ContactModel(
{this.onion,
this.nickname,
this.status,
this.isInvitation,
this.isBlocked,
this.imagePath});
} }
//todo: delete //todo: delete
@ -52,8 +58,7 @@ class ChatMessage {
: o = json['o'], : o = json['o'],
d = json['d']; d = json['d'];
Map<String, dynamic> toJson() => Map<String, dynamic> toJson() => {
{
'o': o, 'o': o,
'd': d, 'd': d,
}; };
@ -73,12 +78,13 @@ class ProfileListState extends ChangeNotifier {
} }
void add(ProfileInfoState newOnion) { void add(ProfileInfoState newOnion) {
print("ProfileListState: adding " + newOnion.onion +" and notifying"); print("ProfileListState: adding " + newOnion.onion + " and notifying");
_onions.add(newOnion); _onions.add(newOnion);
notifyListeners(); notifyListeners();
} }
List<ProfileInfoState> get onions => _onions.sublist(0);//todo: copy?? dont want caller able to bypass changenotifier List<ProfileInfoState> get onions => _onions
.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
} }
class ContactListState extends ChangeNotifier { class ContactListState extends ChangeNotifier {
@ -117,13 +123,16 @@ class ContactListState extends ChangeNotifier {
} }
void updateUnreadMessages(String forOnion, int newVal) { void updateUnreadMessages(String forOnion, int newVal) {
_onions.sort((ContactInfoState a, ContactInfoState b) { return b.unreadMessages - a.unreadMessages; }); _onions.sort((ContactInfoState a, ContactInfoState b) {
return b.unreadMessages - a.unreadMessages;
});
//<todo> if(changed) { //<todo> if(changed) {
notifyListeners(); notifyListeners();
//} </todo> //} </todo>
} }
List<ContactInfoState> get onions => _onions.sublist(0);//todo: copy?? dont want caller able to bypass changenotifier List<ContactInfoState> get onions => _onions
.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
} }
class ProfileInfoState extends ChangeNotifier { class ProfileInfoState extends ChangeNotifier {
@ -132,7 +141,12 @@ class ProfileInfoState extends ChangeNotifier {
String _imagePath = ""; String _imagePath = "";
int _unreadMessages = 0; int _unreadMessages = 0;
ProfileInfoState({this.onion, nickname = "", imagePath = "", unreadMessages = 0,}){ ProfileInfoState({
this.onion,
nickname = "",
imagePath = "",
unreadMessages = 0,
}) {
this._nickname = nickname; this._nickname = nickname;
this._imagePath = imagePath; this._imagePath = imagePath;
this._unreadMessages = unreadMessages; this._unreadMessages = unreadMessages;
@ -174,7 +188,16 @@ class ContactInfoState extends ChangeNotifier {
String _imagePath; String _imagePath;
int _unreadMessages = 0; int _unreadMessages = 0;
ContactInfoState({this.profileOnion, this.onion, nickname = "", isGroup = false, isInvitation = false, isBlocked = false, status = "", imagePath = "",}) { ContactInfoState({
this.profileOnion,
this.onion,
nickname = "",
isGroup = false,
isInvitation = false,
isBlocked = false,
status = "",
imagePath = "",
}) {
this._nickname = nickname; this._nickname = nickname;
this._isGroup = isGroup; this._isGroup = isGroup;
this._isInvitation = isInvitation; this._isInvitation = isInvitation;
@ -212,8 +235,6 @@ class ContactInfoState extends ChangeNotifier {
/// ACN /// /// ACN ///
///////////// /////////////
class AppModel { class AppModel {
final Cwtch cwtch; final Cwtch cwtch;
AppModel({this.cwtch}); AppModel({this.cwtch});

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,40 @@ import 'dart:core';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'opaque.dart';
class Settings extends ChangeNotifier { class Settings extends ChangeNotifier {
Locale locale; Locale locale;
PackageInfo packageInfo; PackageInfo packageInfo;
OpaqueThemeType theme;
void setDark() {
theme = Opaque.dark;
notifyListeners();
}
void setLight() {
theme = Opaque.light;
notifyListeners();
}
OpaqueThemeType current() {
return theme;
}
handleUpdate(dynamic settings) {
print("Settings ${settings}");
switchLocale(Locale(settings["Locale"]));
if (settings["Theme"] == "light") {
this.setLight();
} else {
this.setDark();
}
notifyListeners();
}
initPackageInfo() { initPackageInfo() {
PackageInfo.fromPlatform().then((PackageInfo newPackageInfo) { PackageInfo.fromPlatform().then((PackageInfo newPackageInfo) {
packageInfo = newPackageInfo; packageInfo = newPackageInfo;
@ -21,5 +50,22 @@ class Settings extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Settings(this.locale); Settings(this.locale, this.theme);
}
dynamic asJson() {
var themeString = "light";
if (theme == Opaque.dark) {
themeString = "dark";
}
return {
"Locale": this.locale.languageCode,
"Theme": themeString,
"PreviousPid": -1,
"ExperimentsEnabled": false,
"Experiments": {},
"StateRootPane": 0,
"FirstTime": false
};
}
}

View File

@ -18,13 +18,14 @@ class _AddContactViewState extends State<AddContactView> {
} }
Widget _buildForm() { Widget _buildForm() {
return Center(child:Wrap( return Center(
child: Wrap(
direction: Axis.vertical, direction: Axis.vertical,
spacing: 20.0, spacing: 20.0,
runSpacing: 20.0, runSpacing: 20.0,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(context).profileName), Text(AppLocalizations.of(context).profileName),
Text("peer handle or group invite or server bundle"),//todo Text("peer handle or group invite or server bundle"), //todo
Text(AppLocalizations.of(context).createGroupBtn), Text(AppLocalizations.of(context).createGroupBtn),
], ],
)); ));

View File

@ -12,6 +12,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart'; import '../main.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart';
class AddEditProfileView extends StatefulWidget { class AddEditProfileView extends StatefulWidget {
const AddEditProfileView({Key key}) : super(key: key); const AddEditProfileView({Key key}) : super(key: key);
@ -46,7 +47,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion; ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context).addProfileTitle : AppLocalizations.of(context).editProfileTitle), title: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty
? AppLocalizations.of(context).addProfileTitle
: AppLocalizations.of(context).editProfileTitle),
), ),
body: _buildForm(), body: _buildForm(),
); );
@ -62,8 +65,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
// We use Visibility to hide optional structures when they are not requested. // We use Visibility to hide optional structures when they are not requested.
// We used SizedBox for inter-widget height padding in columns, otherwise elements can render a little too close together. // We used SizedBox for inter-widget height padding in columns, otherwise elements can render a little too close together.
Widget _buildForm() { Widget _buildForm() {
return Consumer<OpaqueTheme>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
isAlwaysShown: true, isAlwaysShown: true,
child: SingleChildScrollView( child: SingleChildScrollView(
@ -77,180 +81,306 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
child: Container( child: Container(
margin: EdgeInsets.all(30), margin: EdgeInsets.all(30),
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ child: Column(
Visibility( mainAxisAlignment: MainAxisAlignment.start,
visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty, crossAxisAlignment: CrossAxisAlignment.stretch,
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ children: [
SizedBox(
width: 120,
height: 120,
child: ClipOval(
child: SizedBox(
width: 120,
height: 120,
child: Container(
color: Colors.white,
width: 120,
height: 120,
child: Image(
image: AssetImage("assets/" + Provider.of<ProfileInfoState>(context).imagePath),
width: 100,
height: 100,
))),
),
)
])),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context).displayNameLabel),
SizedBox(
height: 20,
),
CwtchTextField(
controller: ctrlrNick,
labelText: AppLocalizations.of(context).yourDisplayName,
validator: (value) {
if (value.isEmpty) {
return "Please enter a display name";
}
return null;
},
),
]),
Visibility(
visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context).addressLabel),
SizedBox(
height: 20,
),
CwtchButtonTextField(
controller: ctrlrOnion,
onPressed: _copyOnion,
icon: Icon(Icons.copy),
tooltip: AppLocalizations.of(context).copyBtn,
)
])),
// We only allow setting password types on profile creation
Visibility(
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Radio(
value: false,
groupValue: usePassword,
onChanged: _handleSwitchPassword,
),
Text(
AppLocalizations.of(context).radioNoPassword,
style: TextStyle(color: theme.current().mainTextColor()),
),
Radio(
value: true,
groupValue: usePassword,
onChanged: _handleSwitchPassword,
),
Text(
AppLocalizations.of(context).radioUsePassword,
style: TextStyle(color: theme.current().mainTextColor()),
),
])),
SizedBox(
height: 20,
),
Visibility(
visible: usePassword,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
Visibility( Visibility(
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty, visible:
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Provider.of<ProfileInfoState>(context)
CwtchLabel(label: AppLocalizations.of(context).currentPasswordLabel), .onion
.isNotEmpty,
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
SizedBox(
width: 120,
height: 120,
child: ClipOval(
child: SizedBox(
width: 120,
height: 120,
child: Container(
color: Colors.white,
width: 120,
height: 120,
child: Image(
image: AssetImage("assets/" +
Provider.of<ProfileInfoState>(
context)
.imagePath),
width: 100,
height: 100,
))),
),
)
])),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CwtchLabel(
label: AppLocalizations.of(context)
.displayNameLabel),
SizedBox( SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchTextField(
controller: ctrlrOldPass, controller: ctrlrNick,
labelText:
AppLocalizations.of(context)
.yourDisplayName,
validator: (value) { validator: (value) {
// Password field can be empty when just updating the profile, not on creation if (value.isEmpty) {
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) { return "Please enter a display name";
return AppLocalizations.of(context).passwordErrorEmpty;
} }
return null; return null;
}, },
), ),
SizedBox( ]),
height: 20, Visibility(
), visible:
])), Provider.of<ProfileInfoState>(context)
CwtchLabel(label: AppLocalizations.of(context).password1Label), .onion
.isNotEmpty,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
height: 20,
),
CwtchLabel(
label:
AppLocalizations.of(context)
.addressLabel),
SizedBox(
height: 20,
),
CwtchButtonTextField(
controller: ctrlrOnion,
onPressed: _copyOnion,
icon: Icon(Icons.copy),
tooltip:
AppLocalizations.of(context)
.copyBtn,
)
])),
// We only allow setting password types on profile creation
Visibility(
visible:
Provider.of<ProfileInfoState>(context)
.onion
.isEmpty,
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Radio(
value: false,
groupValue: usePassword,
onChanged: _handleSwitchPassword,
),
Text(
AppLocalizations.of(context)
.radioNoPassword,
style: TextStyle(
color: theme
.current()
.mainTextColor()),
),
Radio(
value: true,
groupValue: usePassword,
onChanged: _handleSwitchPassword,
),
Text(
AppLocalizations.of(context)
.radioUsePassword,
style: TextStyle(
color: theme
.current()
.mainTextColor()),
),
])),
SizedBox( SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( Visibility(
controller: ctrlrPass, visible: usePassword,
validator: (value) { child: Column(
// Password field can be empty when just updating the profile, not on creation mainAxisAlignment:
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) { MainAxisAlignment.start,
return AppLocalizations.of(context).passwordErrorEmpty; crossAxisAlignment:
} CrossAxisAlignment.start,
if (value != ctrlrPass2.value.text) { children: <Widget>[
return AppLocalizations.of(context).passwordErrorMatch; Visibility(
} visible:
return null; Provider.of<ProfileInfoState>(
}, context,
listen: false)
.onion
.isNotEmpty,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CwtchLabel(
label: AppLocalizations
.of(context)
.currentPasswordLabel),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrOldPass,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (Provider.of<ProfileInfoState>(
context,
listen:
false)
.onion
.isEmpty &&
value.isEmpty &&
usePassword) {
return AppLocalizations
.of(context)
.passwordErrorEmpty;
}
return null;
},
),
SizedBox(
height: 20,
),
])),
CwtchLabel(
label:
AppLocalizations.of(context)
.password1Label),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrPass,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (Provider.of<ProfileInfoState>(
context,
listen: false)
.onion
.isEmpty &&
value.isEmpty &&
usePassword) {
return AppLocalizations.of(
context)
.passwordErrorEmpty;
}
if (value !=
ctrlrPass2.value.text) {
return AppLocalizations.of(
context)
.passwordErrorMatch;
}
return null;
},
),
SizedBox(
height: 20,
),
CwtchLabel(
label:
AppLocalizations.of(context)
.password2Label),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrPass2,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (Provider.of<ProfileInfoState>(
context,
listen: false)
.onion
.isEmpty &&
value.isEmpty &&
usePassword) {
return AppLocalizations.of(
context)
.passwordErrorEmpty;
}
if (value !=
ctrlrPass.value.text) {
return AppLocalizations.of(
context)
.passwordErrorMatch;
}
return null;
}),
]),
), ),
SizedBox( SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context).password2Label), ElevatedButton(
SizedBox( onPressed: _createPressed,
height: 20, style: ElevatedButton.styleFrom(
primary: theme
.current()
.defaultButtonColor()),
child: Text(
Provider.of<ProfileInfoState>(context)
.onion
.isEmpty
? AppLocalizations.of(context)
.addNewProfileBtn
: AppLocalizations.of(context)
.saveProfileBtn),
), ),
CwtchPasswordField( Visibility(
controller: ctrlrPass2, visible: Provider.of<ProfileInfoState>(
validator: (value) { context,
// Password field can be empty when just updating the profile, not on creation listen: false)
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) { .onion
return AppLocalizations.of(context).passwordErrorEmpty; .isNotEmpty,
} child: Column(
if (value != ctrlrPass.value.text) { mainAxisAlignment:
return AppLocalizations.of(context).passwordErrorMatch; MainAxisAlignment.start,
} crossAxisAlignment:
return null; CrossAxisAlignment.end,
}), children: [
]), SizedBox(
), height: 20,
SizedBox( ),
height: 20, ElevatedButton.icon(
), onPressed: () {
ElevatedButton( showAlertDialog(context);
onPressed: _createPressed, },
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()), style: ElevatedButton.styleFrom(
child: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context).addNewProfileBtn : AppLocalizations.of(context).saveProfileBtn), primary: theme
), .current()
Visibility( .defaultButtonColor()),
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty, icon: Icon(Icons.delete_forever),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ label: Text(
SizedBox( AppLocalizations.of(context)
height: 20, .deleteBtn),
), )
ElevatedButton.icon( ]))
onPressed: () { ]))))));
showAlertDialog(context);
},
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
icon: Icon(Icons.delete_forever),
label: Text(AppLocalizations.of(context).deleteBtn),
)
]))
]))))));
}); });
}); });
} }
void _copyOnion() { void _copyOnion() {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion)); Clipboard.setData(new ClipboardData(
text: Provider.of<ProfileInfoState>(context, listen: false).onion));
// TODO Toast // TODO Toast
} }
@ -261,10 +391,14 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
if (_formKey.currentState.validate()) { if (_formKey.currentState.validate()) {
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty) { if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty) {
if (usePassword == true) { if (usePassword == true) {
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text); Provider.of<FlwtchState>(context, listen: false)
.cwtch
.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text);
Navigator.of(context).pop(); Navigator.of(context).pop();
} else { } else {
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, "be gay do crime"); Provider.of<FlwtchState>(context, listen: false)
.cwtch
.CreateProfile(ctrlrNick.value.text, "be gay do crime");
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} else { } else {
@ -277,7 +411,11 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
}; };
final json = jsonEncode(event); final json = jsonEncode(event);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, json); Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SendProfileEvent(
Provider.of<ProfileInfoState>(context, listen: false).onion,
json);
Navigator.of(context).pop(); Navigator.of(context).pop();
} else { } else {
// At this points passwords have been validated to be the same and not empty // At this points passwords have been validated to be the same and not empty
@ -288,15 +426,26 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
}; };
final updateNameEventJson = jsonEncode(updateNameEvent); final updateNameEventJson = jsonEncode(updateNameEvent);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, updateNameEventJson); Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SendProfileEvent(
Provider.of<ProfileInfoState>(context, listen: false).onion,
updateNameEventJson);
final updatePasswordEvent = { final updatePasswordEvent = {
"EventType": "ChangePassword", "EventType": "ChangePassword",
"Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text} "Data": {
"Password": ctrlrOldPass.text,
"NewPassword": ctrlrPass.text
}
}; };
final updatePasswordEventJson = jsonEncode(updatePasswordEvent); final updatePasswordEventJson = jsonEncode(updatePasswordEvent);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, updatePasswordEventJson); Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SendProfileEvent(
Provider.of<ProfileInfoState>(context, listen: false).onion,
updatePasswordEventJson);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
@ -310,9 +459,12 @@ showAlertDialog(BuildContext context) {
Widget cancelButton = TextButton( Widget cancelButton = TextButton(
child: Text("Cancel"), child: Text("Cancel"),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), backgroundColor:
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()), MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()), foregroundColor: MaterialStateProperty.all(
Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(
Opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))), padding: MaterialStateProperty.all(EdgeInsets.all(20))),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); // dismiss dialog Navigator.of(context).pop(); // dismiss dialog
@ -320,9 +472,12 @@ showAlertDialog(BuildContext context) {
); );
Widget continueButton = TextButton( Widget continueButton = TextButton(
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), backgroundColor:
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()), MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()), foregroundColor: MaterialStateProperty.all(
Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(
Opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))), padding: MaterialStateProperty.all(EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context).deleteProfileConfirmBtn), child: Text(AppLocalizations.of(context).deleteProfileConfirmBtn),
onPressed: () { onPressed: () {
@ -333,14 +488,7 @@ showAlertDialog(BuildContext context) {
// set up the AlertDialog // set up the AlertDialog
AlertDialog alert = AlertDialog( AlertDialog alert = AlertDialog(
backgroundColor: Provider.of<OpaqueTheme>(context, listen: false).current().backgroundPaneColor(),
title: Text(AppLocalizations.of(context).deleteProfileConfirmBtn), title: Text(AppLocalizations.of(context).deleteProfileConfirmBtn),
titleTextStyle: TextStyle(
color: Provider.of<OpaqueTheme>(context, listen: false).current().mainTextColor(),
),
contentTextStyle: TextStyle(
color: Provider.of<OpaqueTheme>(context, listen: false).current().mainTextColor(),
),
actions: [ actions: [
cancelButton, cancelButton,
continueButton, continueButton,

View File

@ -24,24 +24,31 @@ class _ContactsViewState extends State<ContactsView> {
// Provider.of<ContactListState>(context).onions.forEach((contact) { // Provider.of<ContactListState>(context).onions.forEach((contact) {
// _contacts.putIfAbsent(contact.onion, () => ContactModel(contact); // _contacts.putIfAbsent(contact.onion, () => ContactModel(contact);
// }); // });
// .cwtch.GetContacts(widget.profile.onion).then((jsonContacts) { // .cwtch.GetContacts(widget.profile.onion).then((jsonContacts) {
// print("got contact: $jsonContacts"); // print("got contact: $jsonContacts");
// setState(() { // setState(() {
// List<dynamic> contacts = jsonDecode(jsonContacts); // List<dynamic> contacts = jsonDecode(jsonContacts);
// contacts.forEach((onion) { // contacts.forEach((onion) {
// _contacts.putIfAbsent(onion['onion'], () => ContactModel(onion: onion['onion'], nickname: onion['name'], status: onion['status'])); // _contacts.putIfAbsent(onion['onion'], () => ContactModel(onion: onion['onion'], nickname: onion['name'], status: onion['status']));
// }); // });
// }); // });
// }); // });
// } // }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("%1's contacts".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname ?? Provider.of<ProfileInfoState>(context).onion ?? '')),//todo title: Text("%1's contacts".replaceAll(
"%1",
Provider.of<ProfileInfoState>(context).nickname ??
Provider.of<ProfileInfoState>(context).onion ??
'')), //todo
actions: [ actions: [
IconButton(icon: Icon(Icons.copy), onPressed: _copyOnion,), IconButton(
icon: Icon(Icons.copy),
onPressed: _copyOnion,
),
], ],
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
@ -58,7 +65,7 @@ class _ContactsViewState extends State<ContactsView> {
stream: Provider.of<FlwtchState>(context).appStatus.contactEvents(), stream: Provider.of<FlwtchState>(context).appStatus.contactEvents(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) { builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
final tiles = Provider.of<ContactListState>(context).onions.map( final tiles = Provider.of<ContactListState>(context).onions.map(
(ContactInfoState contact) { (ContactInfoState contact) {
return ChangeNotifierProvider<ContactInfoState>( return ChangeNotifierProvider<ContactInfoState>(
create: (context) => contact, create: (context) => contact,
builder: (context, child) => ContactRow(), builder: (context, child) => ContactRow(),
@ -77,20 +84,20 @@ class _ContactsViewState extends State<ContactsView> {
} }
void _pushAddContact() { void _pushAddContact() {
Navigator.of(context).push( Navigator.of(context).push(MaterialPageRoute<void>(
MaterialPageRoute<void>( builder: (BuildContext context) {
builder: (BuildContext context) { return Provider(
return Provider ( create: (_) => Provider.of<FlwtchState>(context),
create: (_) => Provider.of<FlwtchState>(context), child: AddContactView(),
child: AddContactView(), );
); },
}, ));
)
);
} }
void _copyOnion() { void _copyOnion() {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).copiedClipboardNotification));//todo final snackBar = SnackBar(
content: Text(
AppLocalizations.of(context).copiedClipboardNotification)); //todo
// Find the Scaffold in the widget tree and use it to show a SnackBar. // Find the Scaffold in the widget tree and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }

View File

@ -15,19 +15,23 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var flwtch = Provider.of<FlwtchState>(context); var flwtch = Provider.of<FlwtchState>(context);
return Flex( return Flex(
direction: Axis.horizontal, direction: Axis.horizontal,
children: <Widget>[ children: <Widget>[
Flexible( Flexible(
flex: flwtch.columns[0], flex: flwtch.columns[0],
child: ContactsView(), child: ContactsView(),
), ),
Flexible( Flexible(
flex: flwtch.columns[1], flex: flwtch.columns[1],
child: flwtch.selectedConversation == "" ? child: flwtch.selectedConversation == ""
Center(child:Text("pick a contact")) : //dev ? Center(child: Text("pick a contact"))
Container(child:MessageView(profile:flwtch.selectedProfile, conversationHandle:flwtch.selectedConversation)), : //dev
), Container(
], child: MessageView(
profile: flwtch.selectedProfile,
conversationHandle: flwtch.selectedConversation)),
),
],
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -30,27 +31,34 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
} }
Widget _buildSettingsList() { Widget _buildSettingsList() {
return Consumer<OpaqueTheme>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return Center( return Center(
child: Column(children: [ child: Column(children: [
ListTile( ListTile(
title: Text(AppLocalizations.of(context).settingLanguage, style: TextStyle(color: theme.current().mainTextColor())), title: Text(AppLocalizations.of(context).settingLanguage,
leading: Icon(Icons.language, color: theme.current().mainTextColor()), style: TextStyle(color: theme.current().mainTextColor())),
leading:
Icon(Icons.language, color: theme.current().mainTextColor()),
trailing: DropdownButton( trailing: DropdownButton(
value: Provider.of<Settings>(context).locale.languageCode, value: Provider.of<Settings>(context).locale.languageCode,
onChanged: (String newValue) { onChanged: (String newValue) {
setState(() { setState(() {
Provider.of<Settings>(context, listen: false).switchLocale(Locale(newValue, '')); var settings =
Provider.of<Settings>(context, listen: false);
settings.switchLocale(Locale(newValue, ''));
saveSettings(context);
}); });
}, },
items: AppLocalizations.supportedLocales.map<DropdownMenuItem<String>>((Locale value) { items: AppLocalizations.supportedLocales
.map<DropdownMenuItem<String>>((Locale value) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: value.languageCode, value: value.languageCode,
child: Text(getLanguageFull(context, value.languageCode)), child: Text(getLanguageFull(context, value.languageCode)),
); );
}).toList())), }).toList())),
SwitchListTile( SwitchListTile(
title: Text(AppLocalizations.of(context).settingTheme, style: TextStyle(color: theme.current().mainTextColor())), title: Text(AppLocalizations.of(context).settingTheme,
style: TextStyle(color: theme.current().mainTextColor())),
value: theme.current() == Opaque.light, value: theme.current() == Opaque.light,
onChanged: (bool value) { onChanged: (bool value) {
if (value) { if (value) {
@ -58,22 +66,29 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
} else { } else {
theme.setDark(); theme.setDark();
} }
// Save Settings...
saveSettings(context);
}, },
secondary: Icon(Icons.lightbulb_outline, color: theme.current().mainTextColor()), secondary: Icon(Icons.lightbulb_outline,
color: theme.current().mainTextColor()),
), ),
AboutListTile( AboutListTile(
icon: Icon(Icons.info, color: theme.current().mainTextColor()), icon: Icon(Icons.info, color: theme.current().mainTextColor()),
applicationIcon: Padding( applicationIcon: Padding(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
child: Image( child: Image(
image: AssetImage("assets/knott.png"), image: AssetImage("assets/knott.png"),
width: 128, width: 128,
height: 128, height: 128,
)), )),
applicationName: "Cwtch (Flutter UI)", applicationName: "Cwtch (Flutter UI)",
applicationVersion: AppLocalizations.of(context).version.replaceAll("%1", constructVersionString(Provider.of<Settings>(context).packageInfo)), applicationVersion: AppLocalizations.of(context).version.replaceAll(
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society', "%1",
), constructVersionString(
Provider.of<Settings>(context).packageInfo)),
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
),
])); ]));
}); });
} }
@ -107,3 +122,15 @@ String getLanguageFull(context, String languageCode) {
} }
return languageCode; return languageCode;
} }
saveSettings(context) {
var settings = Provider.of<Settings>(context, listen: false);
final updateSettingsEvent = {
"EventType": "UpdateGlobalSettings",
"Data": {"Data": jsonEncode(settings.asJson())},
};
final updateSettingsEventJson = jsonEncode(updateSettingsEvent);
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SendAppEvent(updateSettingsEventJson);
}

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../model.dart'; import '../model.dart';
@ -6,7 +5,8 @@ import '../opaque.dart';
import '../widgets/messagelist.dart'; import '../widgets/messagelist.dart';
class MessageView extends StatefulWidget { class MessageView extends StatefulWidget {
const MessageView({Key key, this.profile, this.conversationHandle}) : super(key: key); const MessageView({Key key, this.profile, this.conversationHandle})
: super(key: key);
final ProfileInfoState profile; final ProfileInfoState profile;
final String conversationHandle; final String conversationHandle;
@ -25,7 +25,7 @@ class _MessageViewState extends State<MessageView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold ( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(widget.conversationHandle), title: Text(widget.conversationHandle),
actions: [ actions: [
@ -35,12 +35,14 @@ class _MessageViewState extends State<MessageView> {
IconButton(icon: Icon(Icons.settings), onPressed: _pushConvoSettings), IconButton(icon: Icon(Icons.settings), onPressed: _pushConvoSettings),
], ],
), ),
body: MessageList(profile: widget.profile, conversationHandle: widget.conversationHandle), body: MessageList(
profile: widget.profile,
conversationHandle: widget.conversationHandle),
bottomSheet: _buildComposeBox(), bottomSheet: _buildComposeBox(),
); );
} }
void _pushConvoSettings(){} void _pushConvoSettings() {}
void _sendMessage() { void _sendMessage() {
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text); ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
@ -49,37 +51,38 @@ class _MessageViewState extends State<MessageView> {
} }
Widget _buildComposeBox() { Widget _buildComposeBox() {
return Container ( return Container(
color: Opaque.current().backgroundMainColor(), color: Opaque.current().backgroundMainColor(),
height: 100, height: 100,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Expanded ( Expanded(child: TextField(controller: ctrlrCompose)),
child:TextField(controller:ctrlrCompose)
),
SizedBox( SizedBox(
width: 100, width: 100,
height: 80, height: 80,
child: Column( child: Column(children: <Widget>[
children: <Widget>[ ElevatedButton(
ElevatedButton( child:
child: Icon(Icons.send, color: Opaque.current().mainTextColor()), Icon(Icons.send, color: Opaque.current().mainTextColor()),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), backgroundColor: MaterialStateProperty.all(
), onPressed: _sendMessage, Opaque.current().defaultButtonColor()),
), ),
Row ( onPressed: _sendMessage,
children: <Widget>[ ),
SizedBox(width:45, child:ElevatedButton( Row(children: <Widget>[
child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()) SizedBox(
)), width: 45,
SizedBox(width:45, child:ElevatedButton( child: ElevatedButton(
child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()) child: Icon(Icons.emoji_emotions_outlined,
)), color: Opaque.current().mainTextColor()))),
] SizedBox(
) width: 45,
] child: ElevatedButton(
), child: Icon(Icons.attach_file,
color: Opaque.current().mainTextColor()))),
])
]),
), ),
], ],
), ),

View File

@ -25,13 +25,19 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold ( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context).profileName), title: Text(AppLocalizations.of(context).profileName),
actions: [ actions: [
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _testChangingContactInfo), IconButton(
IconButton(icon: Icon(Icons.lock_open), onPressed: _modalUnlockProfiles,), icon: Icon(Icons.bug_report_outlined),
IconButton(icon: Icon(Icons.settings), onPressed: _pushGlobalSettings), onPressed: _testChangingContactInfo),
IconButton(
icon: Icon(Icons.lock_open),
onPressed: _modalUnlockProfiles,
),
IconButton(
icon: Icon(Icons.settings), onPressed: _pushGlobalSettings),
], ],
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
@ -39,75 +45,74 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
tooltip: AppLocalizations.of(context).addNewProfileBtn, tooltip: AppLocalizations.of(context).addNewProfileBtn,
child: const Icon(Icons.add), child: const Icon(Icons.add),
), ),
body: _buildProfileManager(),//_buildSuggestions(), body: _buildProfileManager(), //_buildSuggestions(),
); );
} }
void _testChangingContactInfo() { void _testChangingContactInfo() {
Provider.of<ProfileListState>(context, listen:false).notifyListeners(); Provider.of<ProfileListState>(context, listen: false).notifyListeners();
} }
void _pushGlobalSettings() { void _pushGlobalSettings() {
Navigator.of(context).push( Navigator.of(context).push(MaterialPageRoute<void>(
MaterialPageRoute<void>( builder: (BuildContext context) {
builder: (BuildContext context) { return Provider(
return Provider ( create: (_) => Provider.of<FlwtchState>(context, listen: false),
create: (_) => Provider.of<FlwtchState>(context), child: GlobalSettingsView(),
child: GlobalSettingsView(), );
); },
}, ));
)
);
} }
void _pushAddEditProfile({onion: ""}) { void _pushAddEditProfile({onion: ""}) {
Navigator.of(context).push( Navigator.of(context).push(MaterialPageRoute<void>(
MaterialPageRoute<void>( builder: (BuildContext context) {
builder: (BuildContext context) { return MultiProvider(
return MultiProvider ( providers: [
providers: [ ChangeNotifierProvider<ProfileInfoState>(
ChangeNotifierProvider<ProfileInfoState>(create: (_) => ProfileInfoState(onion: onion),), create: (_) => ProfileInfoState(onion: onion),
], ),
builder: (context, widget) => AddEditProfileView(), ],
); builder: (context, widget) => AddEditProfileView(),
}, );
) },
); ));
} }
void _modalUnlockProfiles() { void _modalUnlockProfiles() {
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return Container( return Container(
height: 200, // bespoke value courtesy of the [TextField] docs height: 200, // bespoke value courtesy of the [TextField] docs
color: Colors.pink[50], color: Colors.pink[50],
child: Center( child: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(context).enterProfilePassword), Text(AppLocalizations.of(context).enterProfilePassword),
TextField( TextField(
obscureText: true, obscureText: true,
controller: ctrlrPassword, controller: ctrlrPassword,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
labelText: AppLocalizations.of(context).password1Label, labelText: AppLocalizations.of(context).password1Label,
),
), ),
), ElevatedButton(
ElevatedButton( child: Text(AppLocalizations.of(context).unlock),
child: Text(AppLocalizations.of(context).unlock), onPressed: () {
onPressed: () { Provider.of<FlwtchState>(context, listen: false)
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text); .cwtch
Navigator.pop(context); .LoadProfiles(ctrlrPassword.value.text);
}, Navigator.pop(context);
), },
], ),
) ],
), )),
); );
}); });
} }
Widget _buildProfileManager() { Widget _buildProfileManager() {
@ -127,4 +132,4 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return ListView(children: divided); return ListView(children: divided);
} }
} }

View File

@ -6,13 +6,8 @@ class SplashView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
print("SplashView build()"); print("SplashView build()");
return Scaffold( return Scaffold(
appBar: AppBar( title: Text("Cwtch")), appBar: AppBar(title: Text("Cwtch")),
body: Center( body: Center(child: Column(children: <Widget>[Text("Loading Cwtch...")])),
child: Column(
children: <Widget>[
Text("Loading Cwtch...")
])
),
); );
} }
} }

View File

@ -15,24 +15,27 @@ class _TripleColumnViewState extends State<TripleColumnView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var flwtch = Provider.of<FlwtchState>(context); var flwtch = Provider.of<FlwtchState>(context);
return Flex( return Flex(direction: Axis.horizontal, children: <Widget>[
direction: Axis.horizontal, Flexible(
children: <Widget>[ flex: flwtch.columns[0],
Flexible( child: ProfileMgrView(),
flex: flwtch.columns[0], ),
child: ProfileMgrView(), Flexible(
), flex: flwtch.columns[1],
Flexible( child: flwtch.selectedProfile == null
flex: flwtch.columns[1], ? Center(child: Text("pick a profile"))
child: flwtch.selectedProfile == null ? Center(child:Text("pick a profile")) : ContactsView(),//dev : ContactsView(), //dev
), ),
Flexible( Flexible(
flex: flwtch.columns[2], flex: flwtch.columns[2],
child: flwtch.selectedConversation == "" ? child: flwtch.selectedConversation == ""
Center(child:Text("pick a contact")) : //dev ? Center(child: Text("pick a contact"))
Container(child:MessageView(profile:flwtch.selectedProfile, conversationHandle:flwtch.selectedConversation)), : //dev
), Container(
] child: MessageView(
); profile: flwtch.selectedProfile,
conversationHandle: flwtch.selectedConversation)),
),
]);
} }
} }

View File

@ -1,11 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_app/settings.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../opaque.dart'; import '../opaque.dart';
// Provides a styled Text Field for use in Form Widgets. // Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator. // Callers must provide a text controller, label helper text and a validator.
class CwtchButtonTextField extends StatefulWidget { class CwtchButtonTextField extends StatefulWidget {
CwtchButtonTextField({this.controller, this.onPressed, this.icon, this.tooltip}); CwtchButtonTextField(
{this.controller, this.onPressed, this.icon, this.tooltip});
final TextEditingController controller; final TextEditingController controller;
final Function onPressed; final Function onPressed;
final Icon icon; final Icon icon;
@ -18,33 +20,50 @@ class CwtchButtonTextField extends StatefulWidget {
class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> { class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<OpaqueTheme>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return TextField( return TextField(
controller: widget.controller, controller: widget.controller,
readOnly: true, readOnly: true,
decoration: InputDecoration( decoration: InputDecoration(
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: widget.onPressed, onPressed: widget.onPressed,
icon: widget.icon, icon: widget.icon,
tooltip: widget.tooltip, tooltip: widget.tooltip,
enableFeedback: true, enableFeedback: true,
color: theme.current().mainTextColor(), color: theme.current().mainTextColor(),
highlightColor: theme.current().defaultButtonColor(), highlightColor: theme.current().defaultButtonColor(),
focusColor: theme.current().defaultButtonActiveColor(), focusColor: theme.current().defaultButtonActiveColor(),
splashColor: theme.current().defaultButtonActiveColor(), splashColor: theme.current().defaultButtonActiveColor(),
), ),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
filled: true, filled: true,
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)), focusedBorder: OutlineInputBorder(
focusedErrorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)), borderRadius: BorderRadius.circular(15.0),
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)), borderSide: BorderSide(
errorStyle: TextStyle (color: theme.current().textfieldErrorColor(), fontWeight: FontWeight.bold,), color: theme.current().textfieldBorderColor(), width: 3.0)),
fillColor: theme.current().textfieldBackgroundColor(), focusedErrorBorder: OutlineInputBorder(
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), borderRadius: BorderRadius.circular(15.0),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0))), borderSide: BorderSide(
color: theme.current().textfieldErrorColor(), width: 3.0)),
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()), errorBorder: OutlineInputBorder(
); borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldErrorColor(), width: 3.0)),
errorStyle: TextStyle(
color: theme.current().textfieldErrorColor(),
fontWeight: FontWeight.bold,
),
fillColor: theme.current().textfieldBackgroundColor(),
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldBorderColor(),
width: 3.0))),
style: TextStyle(
color: theme.current().mainTextColor(),
backgroundColor: theme.current().textfieldBackgroundColor()),
);
}); });
} }
} }

View File

@ -20,11 +20,28 @@ class _ContactRowState extends State<ContactRow> {
width: 60, width: 60,
height: 60, height: 60,
child: ClipOval( child: ClipOval(
child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage("assets/profiles/001-centaur.png"), width:50,height:50,))), child: SizedBox(
width: 60,
height: 60,
child: Container(
color: Colors.white,
width: 60,
height: 60,
child: Image(
image: AssetImage("assets/profiles/001-centaur.png"),
width: 50,
height: 50,
))),
//child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage(contact.imagePath), width:50,height:50,))), //child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage(contact.imagePath), width:50,height:50,))),
), ),
), ),
trailing: contact.isInvitation != null && contact.isInvitation ? Column(children:<Widget>[Icon(Icons.favorite, color: Opaque.current().mainTextColor()),Icon(Icons.delete, color: Opaque.current().mainTextColor())]) : Text("99+"),//(nb: Icons.create is a pencil and we use it for "edit", not create) trailing: contact.isInvitation != null && contact.isInvitation
? Column(children: <Widget>[
Icon(Icons.favorite, color: Opaque.current().mainTextColor()),
Icon(Icons.delete, color: Opaque.current().mainTextColor())
])
: Text(
"99+"), //(nb: Icons.create is a pencil and we use it for "edit", not create)
title: Text( title: Text(
contact.nickname, contact.nickname,
style: Provider.of<FlwtchState>(context).biggerFont, style: Provider.of<FlwtchState>(context).biggerFont,
@ -32,7 +49,7 @@ class _ContactRowState extends State<ContactRow> {
subtitle: Text(contact.status), subtitle: Text(contact.status),
onTap: () { onTap: () {
setState(() { setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen:false); var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.setState(() => flwtch.selectedConversation = contact.onion); flwtch.setState(() => flwtch.selectedConversation = contact.onion);
// case 2/3 handled by Double/TripleColumnView respectively // case 2/3 handled by Double/TripleColumnView respectively
@ -47,7 +64,10 @@ class _ContactRowState extends State<ContactRow> {
MaterialPageRoute<void>( MaterialPageRoute<void>(
builder: (BuildContext builderContext) { builder: (BuildContext builderContext) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider<ProfileInfoState>(create: (_) => Provider.of<ProfileInfoState>(context)),], providers: [
ChangeNotifierProvider<ProfileInfoState>(
create: (_) => Provider.of<ProfileInfoState>(context)),
],
child: MessageView(conversationHandle: handle), child: MessageView(conversationHandle: handle),
); );
}, },

View File

@ -2,12 +2,13 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart';
// Provides a styled Label // Provides a styled Label
// Callers must provide a label text // Callers must provide a label text
// TODO: Integrate this with a settings "zoom" / accessibility setting // TODO: Integrate this with a settings "zoom" / accessibility setting
class CwtchLabel extends StatefulWidget { class CwtchLabel extends StatefulWidget {
CwtchLabel({ this.label}); CwtchLabel({this.label});
final String label; final String label;
@override @override
@ -15,16 +16,13 @@ class CwtchLabel extends StatefulWidget {
} }
class _CwtchLabelState extends State<CwtchLabel> { class _CwtchLabelState extends State<CwtchLabel> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<OpaqueTheme> ( return Consumer<Settings>(builder: (context, theme, child) {
builder: (context, theme, child) { return Text(
return Text( widget.label,
widget.label, style: TextStyle(fontSize: 20, color: theme.current().mainTextColor()),
style: TextStyle(fontSize: 20, color: theme.current().mainTextColor()), );
); });
});
} }
} }

View File

@ -16,16 +16,23 @@ class MessageBubble extends StatefulWidget {
} }
class _MessageBubbleState extends State<MessageBubble> { class _MessageBubbleState extends State<MessageBubble> {
String d="", ts=""; String d = "", ts = "";
bool ack=false; bool ack = false;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
print("requesting message " + widget.messageIndex.toString()); print("requesting message " + widget.messageIndex.toString());
Provider.of<FlwtchState>(context).cwtch.GetMessage(widget.profile.onion, widget.contactOnion, widget.messageIndex).then((jsonMessage){ Provider.of<FlwtchState>(context)
print("got message: " + widget.messageIndex.toString() + ": " + jsonMessage); .cwtch
.GetMessage(
widget.profile.onion, widget.contactOnion, widget.messageIndex)
.then((jsonMessage) {
print("got message: " +
widget.messageIndex.toString() +
": " +
jsonMessage);
dynamic messageWrapper = jsonDecode(jsonMessage); dynamic messageWrapper = jsonDecode(jsonMessage);
dynamic message = jsonDecode(messageWrapper['Message']); dynamic message = jsonDecode(messageWrapper['Message']);
setState(() { setState(() {
@ -57,11 +64,12 @@ class _MessageBubbleState extends State<MessageBubble> {
), ),
subtitle: Row( subtitle: Row(
children: [ children: [
Text(""+widget.messageIndex.toString()), Text("" + widget.messageIndex.toString()),
ack ? Icon(Icons.check_circle_outline, ack
color: Opaque.current().mainTextColor()) : Icon( ? Icon(Icons.check_circle_outline,
Icons.hourglass_bottom_outlined, color: Opaque.current().mainTextColor())
color: Opaque.current().mainTextColor()) : Icon(Icons.hourglass_bottom_outlined,
color: Opaque.current().mainTextColor())
], ],
), ),
), ),

View File

@ -10,7 +10,8 @@ class MessageList extends StatefulWidget {
final ProfileInfoState profile; final ProfileInfoState profile;
final String conversationHandle; final String conversationHandle;
const MessageList({Key key, this.profile, this.conversationHandle}) : super(key: key); const MessageList({Key key, this.profile, this.conversationHandle})
: super(key: key);
@override @override
_MessageListState createState() => _MessageListState(); _MessageListState createState() => _MessageListState();
@ -30,18 +31,17 @@ class _MessageListState extends State<MessageList> {
} }
return ProxyProvider0( return ProxyProvider0(
update: (_, __) => MessageCounter(conversationNumMessages), update: (_, __) => MessageCounter(conversationNumMessages),
child: ListView.builder( child: ListView.builder(
itemCount: conversationNumMessages, itemCount: conversationNumMessages,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return MessageBubble( return MessageBubble(
profile: Provider.of<ProfileInfoState>(context), profile: Provider.of<ProfileInfoState>(context),
contactOnion: widget.conversationHandle, contactOnion: widget.conversationHandle,
messageIndex: index, messageIndex: index,
); );
}, },
) ));
);
} }
Future _updateMessageCount(BuildContext context) async { Future _updateMessageCount(BuildContext context) async {
@ -50,8 +50,14 @@ class _MessageListState extends State<MessageList> {
return; return;
} }
Provider.of<FlwtchState>(context, listen: false).cwtch.NumMessages(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.conversationHandle).then((n) { Provider.of<FlwtchState>(context, listen: false)
if (n != conversationNumMessages) setState(() => conversationNumMessages = n); .cwtch
.NumMessages(
Provider.of<ProfileInfoState>(context, listen: false).onion,
widget.conversationHandle)
.then((n) {
if (n != conversationNumMessages)
setState(() => conversationNumMessages = n);
}); });
} }
} }
@ -59,4 +65,4 @@ class _MessageListState extends State<MessageList> {
class MessageCounter { class MessageCounter {
MessageCounter(this.x); MessageCounter(this.x);
int x = 0; int x = 0;
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart';
// Provides a styled Password Input Field for use in Form Widgets. // Provides a styled Password Input Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator. // Callers must provide a text controller, label helper text and a validator.
@ -17,7 +18,7 @@ class CwtchPasswordField extends StatefulWidget {
class _CwtchTextFieldState extends State<CwtchPasswordField> { class _CwtchTextFieldState extends State<CwtchPasswordField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<OpaqueTheme>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return TextFormField( return TextFormField(
controller: widget.controller, controller: widget.controller,
validator: widget.validator, validator: widget.validator,
@ -25,18 +26,32 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
enableSuggestions: false, enableSuggestions: false,
autocorrect: false, autocorrect: false,
decoration: InputDecoration( decoration: InputDecoration(
errorStyle: TextStyle( errorStyle: TextStyle(
color: theme.current().textfieldErrorColor(), color: theme.current().textfieldErrorColor(),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)), focusedBorder: OutlineInputBorder(
focusedErrorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)), borderRadius: BorderRadius.circular(15.0),
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)), borderSide: BorderSide(
filled: true, color: theme.current().textfieldBorderColor(), width: 3.0)),
fillColor: theme.current().textfieldBackgroundColor(), focusedErrorBorder: OutlineInputBorder(
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)), borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldErrorColor(), width: 3.0)),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldErrorColor(), width: 3.0)),
filled: true,
fillColor: theme.current().textfieldBackgroundColor(),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldBorderColor(), width: 3.0)),
), ),
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()), style: TextStyle(
color: theme.current().mainTextColor(),
backgroundColor: theme.current().textfieldBackgroundColor()),
); );
}); });
} }

View File

@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../main.dart'; import '../main.dart';
import '../model.dart'; import '../model.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart';
class ProfileRow extends StatefulWidget { class ProfileRow extends StatefulWidget {
@override @override
@ -22,13 +23,30 @@ class _ProfileRowState extends State<ProfileRow> {
width: 60, width: 60,
height: 60, height: 60,
child: ClipOval( child: ClipOval(
child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage("assets/" + profile.imagePath), width:50,height:50,))), child: SizedBox(
width: 60,
height: 60,
child: Container(
color: Colors.white,
width: 60,
height: 60,
child: Image(
image: AssetImage("assets/" + profile.imagePath),
width: 50,
height: 50,
))),
), ),
) , ),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.create, color: Provider.of<OpaqueTheme>(context).current().mainTextColor()), icon: Icon(Icons.create,
onPressed: () { _pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath); }, color: Provider.of<Settings>(context).current().mainTextColor()),
),//(nb: Icons.create is a pencil and we use it for "edit", not create) onPressed: () {
_pushAddEditProfile(
onion: profile.onion,
displayName: profile.nickname,
profileImage: profile.imagePath);
},
), //(nb: Icons.create is a pencil and we use it for "edit", not create)
title: Text( title: Text(
profile.nickname, profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont, style: Provider.of<FlwtchState>(context).biggerFont,
@ -36,7 +54,7 @@ class _ProfileRowState extends State<ProfileRow> {
subtitle: Text(profile.onion), subtitle: Text(profile.onion),
onTap: () { onTap: () {
setState(() { setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen:false); var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.cwtch.SelectProfile(profile.onion); flwtch.cwtch.SelectProfile(profile.onion);
flwtch.setState(() { flwtch.setState(() {
flwtch.selectedProfile = profile; flwtch.selectedProfile = profile;
@ -44,8 +62,12 @@ class _ProfileRowState extends State<ProfileRow> {
}); });
switch (flwtch.columns.length) { switch (flwtch.columns.length) {
case 1: _pushContactList(profile, false); break; case 1:
case 2: _pushContactList(profile, true); break; _pushContactList(profile, false);
break;
case 2:
_pushContactList(profile, true);
break;
} // case 3: handled by TripleColumnView } // case 3: handled by TripleColumnView
}); });
}, },
@ -59,28 +81,33 @@ class _ProfileRowState extends State<ProfileRow> {
return MultiProvider( return MultiProvider(
providers: [ providers: [
ChangeNotifierProvider<ProfileInfoState>.value(value: profile), ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
ChangeNotifierProvider<ContactListState>(create: (_) => ContactListState(Provider.of<FlwtchState>(buildcontext).cwtch, profile.onion),), ChangeNotifierProvider<ContactListState>(
create: (_) => ContactListState(
Provider.of<FlwtchState>(buildcontext).cwtch,
profile.onion),
),
], ],
builder: (context, widget) => includeDoublePane ? DoubleColumnView() : ContactsView(), builder: (context, widget) =>
includeDoublePane ? DoubleColumnView() : ContactsView(),
); );
}, },
), ),
); );
} }
void _pushAddEditProfile({onion: "", displayName: "", profileImage: ""}) { void _pushAddEditProfile({onion: "", displayName: "", profileImage: ""}) {
Navigator.of(context).push( Navigator.of(context).push(MaterialPageRoute<void>(
MaterialPageRoute<void>( builder: (BuildContext context) {
builder: (BuildContext context) { return MultiProvider(
return MultiProvider ( providers: [
providers: [ ChangeNotifierProvider<ProfileInfoState>(
ChangeNotifierProvider<ProfileInfoState>(create: (_) => ProfileInfoState(onion: onion, nickname:displayName, imagePath: profileImage),), create: (_) => ProfileInfoState(
], onion: onion, nickname: displayName, imagePath: profileImage),
builder: (context, widget) => AddEditProfileView(), ),
); ],
}, builder: (context, widget) => AddEditProfileView(),
) );
); },
));
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart';
// Provides a styled Text Field for use in Form Widgets. // Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator. // Callers must provide a text controller, label helper text and a validator.
@ -17,26 +18,44 @@ class CwtchTextField extends StatefulWidget {
class _CwtchTextFieldState extends State<CwtchTextField> { class _CwtchTextFieldState extends State<CwtchTextField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<OpaqueTheme>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return TextFormField( return TextFormField(
controller: widget.controller, controller: widget.controller,
validator: widget.validator, validator: widget.validator,
decoration: InputDecoration( decoration: InputDecoration(
labelText: widget.labelText,
labelText: widget.labelText, labelStyle: TextStyle(
labelStyle: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()), color: theme.current().mainTextColor(),
floatingLabelBehavior: FloatingLabelBehavior.never, backgroundColor: theme.current().textfieldBackgroundColor()),
filled: true, floatingLabelBehavior: FloatingLabelBehavior.never,
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)), filled: true,
focusedErrorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)), focusedBorder: OutlineInputBorder(
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)), borderRadius: BorderRadius.circular(15.0),
errorStyle: TextStyle (color: theme.current().textfieldErrorColor(), fontWeight: FontWeight.bold,), borderSide: BorderSide(
fillColor: theme.current().textfieldBackgroundColor(), color: theme.current().textfieldBorderColor(), width: 3.0)),
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), focusedErrorBorder: OutlineInputBorder(
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0))), borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()), color: theme.current().textfieldErrorColor(), width: 3.0)),
); errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldErrorColor(), width: 3.0)),
errorStyle: TextStyle(
color: theme.current().textfieldErrorColor(),
fontWeight: FontWeight.bold,
),
fillColor: theme.current().textfieldBackgroundColor(),
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldBorderColor(),
width: 3.0))),
style: TextStyle(
color: theme.current().mainTextColor(),
backgroundColor: theme.current().textfieldBackgroundColor()),
);
}); });
} }
} }

View File

@ -15,17 +15,15 @@ class _TorStatusState extends State<TorStatusLabel> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Builder( return Builder(
builder: (context2) => StreamBuilder<String>( builder: (context2) => StreamBuilder<String>(
stream: Provider.of<FlwtchState>(context).appStatus.torStatus(), stream: Provider.of<FlwtchState>(context).appStatus.torStatus(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) { builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return Text( return Text(
snapshot.hasData ? snapshot.hasData
snapshot.data : AppLocalizations.of(context).loadingTor, ? snapshot.data
style: Theme : AppLocalizations.of(context).loadingTor,
.of(context) style: Theme.of(context).textTheme.headline4,
.textTheme );
.headline4, },
); ));
},
));
} }
} }