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) ?: "";
Cwtch.sendProfileEvent(onion, jsonEvent);
}
"SendAppEvent" -> {
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
Cwtch.sendAppEvent(jsonEvent);
}
else -> result.notImplemented()
}
}

View File

@ -5,6 +5,7 @@ abstract class Cwtch {
void CreateProfile(String nick, String pass);
void LoadProfiles(String pass);
void SendProfileEvent(String onion, String jsonEvent);
void SendAppEvent(String jsonEvent);
Future<String> ACNEvents();
Future<String> ContactEvents();
@ -15,4 +16,4 @@ abstract class Cwtch {
Future<int> NumMessages(String profile, String handle);
Future<String> GetMessage(String profile, String handle, int index);
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 '../settings.dart';
// 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
class CwtchNotifier {
ProfileListState profileCN;
Settings settings;
CwtchNotifier(ProfileListState pcn) {
CwtchNotifier(ProfileListState pcn, Settings settingsCN) {
profileCN = pcn;
settings = settingsCN;
}
void handleMessage(String type, dynamic data) {
switch (type) {
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;
default:
print("unhandled gomobile appbus event: ${type}");

View File

@ -15,41 +15,55 @@ import '../model.dart';
/// Cwtch API ///
/////////////////////
typedef start_cwtch_function = Void Function(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 start_cwtch_function = Void Function(
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 VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef void_from_string_string_function = Void Function(
Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringFn = void Function(
Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef access_cwtch_eventbus_function = 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 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 get_json_blob_void_function = Pointer<Utf8> Function();
typedef GetJsonBlobVoidFn = Pointer<Utf8> Function();
typedef get_json_blob_string_function = Pointer<Utf8> Function(Pointer<Utf8> str, Int32 length);
typedef GetJsonBlobStringFn = Pointer<Utf8> Function(Pointer<Utf8> str,int len);
typedef get_json_blob_string_function = Pointer<Utf8> Function(
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) {
typedef get_int_from_str_str_function = Int32 Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef GetIntFromStrStrFn = int Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef get_int_from_str_str_function = Int32 Function(
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 {
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32);
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(
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 {
typedef get_json_blob_from_str_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32, Int32);
typedef GetJsonBlobFromStrStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
typedef get_json_blob_from_str_str_int_int_function = Pointer<Utf8> Function(
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 ACNEventsFn = Pointer<Utf8> Function();
typedef ACNEventsFn = Pointer<Utf8> Function();
class CwtchFfi implements Cwtch {
DynamicLibrary library;
@ -72,17 +86,18 @@ class CwtchFfi implements Cwtch {
var cwtchDir = path.join(home, ".cwtch/dev/");
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
final StartCwtch = startCwtchC.asFunction<StartCwtchFn>();
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
var _receivePort = ReceivePort();
cwtchIsolate = await Isolate.spawn(_checkAppbusEvents, _receivePort.sendPort);
cwtchIsolate =
await Isolate.spawn(_checkAppbusEvents, _receivePort.sendPort);
_receivePort.listen((message) {
var env = jsonDecode(message);
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
static Stream<String> pollAppbusEvents() async* {
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>();
while (true) {
@ -120,7 +136,9 @@ class CwtchFfi implements Cwtch {
// ignore: non_constant_identifier_names
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
final SelectProfile = selectProfileC.asFunction<GetJsonBlobStringFn>();
final ut8Onion = onion.toNativeUtf8();
@ -129,7 +147,9 @@ class CwtchFfi implements Cwtch {
// ignore: non_constant_identifier_names
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
final CreateProfile = createProfileC.asFunction<VoidFromStringStringFn>();
final utf8nick = nick.toNativeUtf8();
@ -139,7 +159,8 @@ class CwtchFfi implements Cwtch {
// ignore: non_constant_identifier_names
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
final LoadProfiles = loadProfileC.asFunction<StringFn>();
final ut8pass = pass.toNativeUtf8();
@ -147,8 +168,8 @@ class CwtchFfi implements Cwtch {
}
Future<String> ACNEvents() async {
var acnEventsC = library.lookup<NativeFunction<acn_events_function>>(
"c_ACNEvents");
var acnEventsC =
library.lookup<NativeFunction<acn_events_function>>("c_ACNEvents");
// ignore: non_constant_identifier_names
final ACNEvents = acnEventsC.asFunction<ACNEventsFn>();
@ -157,10 +178,9 @@ class CwtchFfi implements Cwtch {
return event;
}
Future<String> ContactEvents() async {
var acnEventsC = library.lookup<NativeFunction<acn_events_function>>(
"c_ContactEvents");
var acnEventsC =
library.lookup<NativeFunction<acn_events_function>>("c_ContactEvents");
// ignore: non_constant_identifier_names
final ContactEvents = acnEventsC.asFunction<ACNEventsFn>();
@ -170,7 +190,8 @@ class CwtchFfi implements Cwtch {
}
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
final GetProfiles = getProfilesC.asFunction<GetJsonBlobVoidFn>();
@ -180,7 +201,8 @@ class CwtchFfi implements Cwtch {
}
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
final GetContacts = getContactsC.asFunction<GetJsonBlobStringFn>();
final utf8onion = onion.toNativeUtf8();
@ -190,44 +212,66 @@ class CwtchFfi implements Cwtch {
}
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
final NumMessages = numMessagesC.asFunction<GetIntFromStrStrFn>();
final utf8profile = profile.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;
}
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
final GetMessage = getMessageC.asFunction<GetJsonBlobFromStrStrIntFn>();
final utf8profile = profile.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();
return jsonMessage;
}
Future<String> 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");
Future<String> 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
final GetMessages = getMessagesC.asFunction<GetJsonBlobFromStrStrIntIntFn>();
final GetMessages =
getMessagesC.asFunction<GetJsonBlobFromStrStrIntIntFn>();
final utf8profile = profile.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();
return jsonMessages;
}
@override
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
final SendAppBusEvent = sendAppBusEvent.asFunction<StringStringFn>();
final utf8onion = onion.toNativeUtf8();
final utf8json = json.toNativeUtf8();
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 {
static const appInfoPlatform = const MethodChannel('test.flutter.dev/applicationInfo');
static const appInfoPlatform =
const MethodChannel('test.flutter.dev/applicationInfo');
static const cwtchPlatform = const MethodChannel('cwtch');
final appbusEventChannelName = 'test.flutter.dev/eventBus';
@ -47,7 +48,8 @@ class CwtchGomobile implements Cwtch {
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch/dev/");
String torPath = path.join(await androidLibraryDir, "libtor.so");
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
@ -59,7 +61,7 @@ class CwtchGomobile implements Cwtch {
}
void SelectProfile(String onion) {
cwtchPlatform.invokeMethod("SelectProfile", {"profile" : onion});
cwtchPlatform.invokeMethod("SelectProfile", {"profile": onion});
}
void CreateProfile(String nick, String pass) {
@ -84,26 +86,34 @@ class CwtchGomobile implements Cwtch {
}
Future<String> GetContacts(String onion) {
return cwtchPlatform.invokeMethod("GetContacts", {"profile" : onion});
return cwtchPlatform.invokeMethod("GetContacts", {"profile": onion});
}
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) {
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) {
return cwtchPlatform.invokeMethod("GetMessage", {"profile" : profile, "contact": handle, "start": start, "end": end});
Future<String> GetMessages(
String profile, String handle, int start, int end) {
return cwtchPlatform.invokeMethod("GetMessage",
{"profile": profile, "contact": handle, "start": start, "end": end});
}
@override
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 'package:flutter_gen/gen_l10n/app_localizations.dart';
var GlobalSettings = Settings(Locale("en", ''), Opaque.dark);
void main() => runApp(Flwtch());
class Flwtch extends StatefulWidget {
@ -39,7 +41,7 @@ class FlwtchState extends State<Flwtch> {
cwtchInit = false;
profs = ProfileListState();
var cwtchNotifier = new CwtchNotifier(profs);
var cwtchNotifier = new CwtchNotifier(profs, GlobalSettings);
if (Platform.isAndroid) {
cwtch = CwtchGomobile(cwtchNotifier);
@ -56,20 +58,26 @@ class FlwtchState extends State<Flwtch> {
appStatus = AppModel(cwtch: cwtch);
}
ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider(create: (context) => Settings(Locale("en", '')));
ChangeNotifierProvider<OpaqueTheme> getOpaqueProvider() => ChangeNotifierProvider(create: (context) => OpaqueTheme(Opaque.dark));
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
ChangeNotifierProvider<Settings> getSettingsProvider() =>
ChangeNotifierProvider(create: (context) => GlobalSettings);
Provider<FlwtchState> getFlwtchStateProvider() =>
Provider<FlwtchState>(create: (_) => this);
ChangeNotifierProvider<ProfileListState> getProfileListProvider() =>
ChangeNotifierProvider(create: (context) => profs);
@override
Widget build(BuildContext context) {
appStatus = AppModel(cwtch: cwtch);
return MultiProvider(
providers: [getFlwtchStateProvider(), getProfileListProvider(), getOpaqueProvider(), getSettingsProvider()],
providers: [
getFlwtchStateProvider(),
getProfileListProvider(),
getSettingsProvider()
],
builder: (context, widget) {
Provider.of<Settings>(context).initPackageInfo();
return Consumer<OpaqueTheme>(
return Consumer<Settings>(
builder: (context, opaque, child) => MaterialApp(
locale: Provider.of<Settings>(context).locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
@ -83,42 +91,46 @@ class FlwtchState extends State<Flwtch> {
accentColor: opaque.current().defaultButtonColor(),
buttonColor: opaque.current().defaultButtonColor(),
backgroundColor: opaque.current().backgroundMainColor(),
iconTheme: IconThemeData (
iconTheme: IconThemeData(
color: opaque.current().mainTextColor(),
),
cardColor: opaque.current().backgroundMainColor(),
textButtonTheme: TextButtonThemeData (
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))
),
),
dialogTheme: DialogTheme (
backgroundColor: opaque.current().backgroundPaneColor(),
titleTextStyle: TextStyle( color: opaque.current().mainTextColor()),
contentTextStyle: TextStyle( color: opaque.current().mainTextColor())
backgroundColor: MaterialStateProperty.all(
opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(
opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(
opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
),
dialogTheme: DialogTheme(
backgroundColor: opaque.current().backgroundPaneColor(),
titleTextStyle:
TextStyle(color: opaque.current().mainTextColor()),
contentTextStyle:
TextStyle(color: opaque.current().mainTextColor())),
textTheme: TextTheme(
headline1: TextStyle( color: opaque.current().mainTextColor()),
headline2: TextStyle( color: opaque.current().mainTextColor()),
headline3: TextStyle( color: opaque.current().mainTextColor()),
headline4: TextStyle( color: opaque.current().mainTextColor()),
headline5: TextStyle( color: opaque.current().mainTextColor()),
headline6: TextStyle( color: opaque.current().mainTextColor()),
bodyText1: TextStyle( color: opaque.current().mainTextColor()),
bodyText2: TextStyle( color: opaque.current().mainTextColor()),
subtitle1: TextStyle( color: opaque.current().mainTextColor()),
subtitle2: TextStyle( color: opaque.current().mainTextColor()),
caption: TextStyle( color: opaque.current().mainTextColor()),
button: TextStyle( color: opaque.current().mainTextColor()),
overline: TextStyle( color: opaque.current().mainTextColor())
),
headline1: TextStyle(color: opaque.current().mainTextColor()),
headline2: TextStyle(color: opaque.current().mainTextColor()),
headline3: TextStyle(color: opaque.current().mainTextColor()),
headline4: TextStyle(color: opaque.current().mainTextColor()),
headline5: TextStyle(color: opaque.current().mainTextColor()),
headline6: TextStyle(color: opaque.current().mainTextColor()),
bodyText1: TextStyle(color: opaque.current().mainTextColor()),
bodyText2: TextStyle(color: opaque.current().mainTextColor()),
subtitle1: TextStyle(color: opaque.current().mainTextColor()),
subtitle2: TextStyle(color: opaque.current().mainTextColor()),
caption: TextStyle(color: opaque.current().mainTextColor()),
button: TextStyle(color: opaque.current().mainTextColor()),
overline: TextStyle(color: opaque.current().mainTextColor())),
),
// from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(),
// 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 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
@ -52,8 +58,7 @@ class ChatMessage {
: o = json['o'],
d = json['d'];
Map<String, dynamic> toJson() =>
{
Map<String, dynamic> toJson() => {
'o': o,
'd': d,
};
@ -73,12 +78,13 @@ class ProfileListState extends ChangeNotifier {
}
void add(ProfileInfoState newOnion) {
print("ProfileListState: adding " + newOnion.onion +" and notifying");
print("ProfileListState: adding " + newOnion.onion + " and notifying");
_onions.add(newOnion);
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 {
@ -117,13 +123,16 @@ class ContactListState extends ChangeNotifier {
}
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) {
notifyListeners();
//} </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 {
@ -132,7 +141,12 @@ class ProfileInfoState extends ChangeNotifier {
String _imagePath = "";
int _unreadMessages = 0;
ProfileInfoState({this.onion, nickname = "", imagePath = "", unreadMessages = 0,}){
ProfileInfoState({
this.onion,
nickname = "",
imagePath = "",
unreadMessages = 0,
}) {
this._nickname = nickname;
this._imagePath = imagePath;
this._unreadMessages = unreadMessages;
@ -174,7 +188,16 @@ class ContactInfoState extends ChangeNotifier {
String _imagePath;
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._isGroup = isGroup;
this._isInvitation = isInvitation;
@ -212,8 +235,6 @@ class ContactInfoState extends ChangeNotifier {
/// ACN ///
/////////////
class AppModel {
final Cwtch 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:package_info_plus/package_info_plus.dart';
import 'opaque.dart';
class Settings extends ChangeNotifier {
Locale locale;
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() {
PackageInfo.fromPlatform().then((PackageInfo newPackageInfo) {
packageInfo = newPackageInfo;
@ -21,5 +50,22 @@ class Settings extends ChangeNotifier {
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() {
return Center(child:Wrap(
return Center(
child: Wrap(
direction: Axis.vertical,
spacing: 20.0,
runSpacing: 20.0,
children: <Widget>[
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),
],
));

View File

@ -12,6 +12,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart';
import '../opaque.dart';
import '../settings.dart';
class AddEditProfileView extends StatefulWidget {
const AddEditProfileView({Key key}) : super(key: key);
@ -46,7 +47,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
return Scaffold(
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(),
);
@ -62,8 +65,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
// 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.
Widget _buildForm() {
return Consumer<OpaqueTheme>(builder: (context, theme, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Consumer<Settings>(builder: (context, theme, child) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar(
isAlwaysShown: true,
child: SingleChildScrollView(
@ -77,180 +81,306 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
child: Container(
margin: EdgeInsets.all(30),
padding: EdgeInsets.all(20),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Visibility(
visible: Provider.of<ProfileInfoState>(context).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(
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>[
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Visibility(
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context).currentPasswordLabel),
visible:
Provider.of<ProfileInfoState>(context)
.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(
height: 20,
),
CwtchPasswordField(
controller: ctrlrOldPass,
CwtchTextField(
controller: ctrlrNick,
labelText:
AppLocalizations.of(context)
.yourDisplayName,
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.isEmpty) {
return "Please enter a display name";
}
return null;
},
),
SizedBox(
height: 20,
),
])),
CwtchLabel(label: AppLocalizations.of(context).password1Label),
]),
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,
),
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;
},
Visibility(
visible: usePassword,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Visibility(
visible:
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(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context).password2Label),
SizedBox(
height: 20,
ElevatedButton(
onPressed: _createPressed,
style: ElevatedButton.styleFrom(
primary: theme
.current()
.defaultButtonColor()),
child: Text(
Provider.of<ProfileInfoState>(context)
.onion
.isEmpty
? AppLocalizations.of(context)
.addNewProfileBtn
: AppLocalizations.of(context)
.saveProfileBtn),
),
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(
height: 20,
),
ElevatedButton(
onPressed: _createPressed,
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
child: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context).addNewProfileBtn : AppLocalizations.of(context).saveProfileBtn),
),
Visibility(
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox(
height: 20,
),
ElevatedButton.icon(
onPressed: () {
showAlertDialog(context);
},
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
icon: Icon(Icons.delete_forever),
label: Text(AppLocalizations.of(context).deleteBtn),
)
]))
]))))));
Visibility(
visible: Provider.of<ProfileInfoState>(
context,
listen: false)
.onion
.isNotEmpty,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
SizedBox(
height: 20,
),
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() {
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
}
@ -261,10 +391,14 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
if (_formKey.currentState.validate()) {
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty) {
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();
} 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();
}
} else {
@ -277,7 +411,11 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
};
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();
} else {
// 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);
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 = {
"EventType": "ChangePassword",
"Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text}
"Data": {
"Password": ctrlrOldPass.text,
"NewPassword": ctrlrPass.text
}
};
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();
}
@ -310,9 +459,12 @@ showAlertDialog(BuildContext context) {
Widget cancelButton = TextButton(
child: Text("Cancel"),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()),
backgroundColor:
MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(
Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(
Opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
onPressed: () {
Navigator.of(context).pop(); // dismiss dialog
@ -320,9 +472,12 @@ showAlertDialog(BuildContext context) {
);
Widget continueButton = TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()),
backgroundColor:
MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(
Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(
Opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context).deleteProfileConfirmBtn),
onPressed: () {
@ -333,14 +488,7 @@ showAlertDialog(BuildContext context) {
// set up the AlertDialog
AlertDialog alert = AlertDialog(
backgroundColor: Provider.of<OpaqueTheme>(context, listen: false).current().backgroundPaneColor(),
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: [
cancelButton,
continueButton,

View File

@ -24,24 +24,31 @@ class _ContactsViewState extends State<ContactsView> {
// Provider.of<ContactListState>(context).onions.forEach((contact) {
// _contacts.putIfAbsent(contact.onion, () => ContactModel(contact);
// });
// .cwtch.GetContacts(widget.profile.onion).then((jsonContacts) {
// print("got contact: $jsonContacts");
// setState(() {
// List<dynamic> contacts = jsonDecode(jsonContacts);
// contacts.forEach((onion) {
// _contacts.putIfAbsent(onion['onion'], () => ContactModel(onion: onion['onion'], nickname: onion['name'], status: onion['status']));
// });
// });
// });
// .cwtch.GetContacts(widget.profile.onion).then((jsonContacts) {
// print("got contact: $jsonContacts");
// setState(() {
// List<dynamic> contacts = jsonDecode(jsonContacts);
// contacts.forEach((onion) {
// _contacts.putIfAbsent(onion['onion'], () => ContactModel(onion: onion['onion'], nickname: onion['name'], status: onion['status']));
// });
// });
// });
// }
@override
Widget build(BuildContext context) {
return Scaffold(
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: [
IconButton(icon: Icon(Icons.copy), onPressed: _copyOnion,),
IconButton(
icon: Icon(Icons.copy),
onPressed: _copyOnion,
),
],
),
floatingActionButton: FloatingActionButton(
@ -58,7 +65,7 @@ class _ContactsViewState extends State<ContactsView> {
stream: Provider.of<FlwtchState>(context).appStatus.contactEvents(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
final tiles = Provider.of<ContactListState>(context).onions.map(
(ContactInfoState contact) {
(ContactInfoState contact) {
return ChangeNotifierProvider<ContactInfoState>(
create: (context) => contact,
builder: (context, child) => ContactRow(),
@ -77,20 +84,20 @@ class _ContactsViewState extends State<ContactsView> {
}
void _pushAddContact() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider (
create: (_) => Provider.of<FlwtchState>(context),
child: AddContactView(),
);
},
)
);
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider(
create: (_) => Provider.of<FlwtchState>(context),
child: AddContactView(),
);
},
));
}
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.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}

View File

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

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter/material.dart';
@ -30,27 +31,34 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
}
Widget _buildSettingsList() {
return Consumer<OpaqueTheme>(builder: (context, theme, child) {
return Consumer<Settings>(builder: (context, theme, child) {
return Center(
child: Column(children: [
ListTile(
title: Text(AppLocalizations.of(context).settingLanguage, style: TextStyle(color: theme.current().mainTextColor())),
leading: Icon(Icons.language, color: theme.current().mainTextColor()),
title: Text(AppLocalizations.of(context).settingLanguage,
style: TextStyle(color: theme.current().mainTextColor())),
leading:
Icon(Icons.language, color: theme.current().mainTextColor()),
trailing: DropdownButton(
value: Provider.of<Settings>(context).locale.languageCode,
onChanged: (String newValue) {
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>(
value: value.languageCode,
child: Text(getLanguageFull(context, value.languageCode)),
);
}).toList())),
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,
onChanged: (bool value) {
if (value) {
@ -58,22 +66,29 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
} else {
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(
icon: Icon(Icons.info, color: theme.current().mainTextColor()),
applicationIcon: Padding(
padding: EdgeInsets.all(20),
child: Image(
image: AssetImage("assets/knott.png"),
width: 128,
height: 128,
)),
applicationName: "Cwtch (Flutter UI)",
applicationVersion: AppLocalizations.of(context).version.replaceAll("%1", constructVersionString(Provider.of<Settings>(context).packageInfo)),
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
),
icon: Icon(Icons.info, color: theme.current().mainTextColor()),
applicationIcon: Padding(
padding: EdgeInsets.all(20),
child: Image(
image: AssetImage("assets/knott.png"),
width: 128,
height: 128,
)),
applicationName: "Cwtch (Flutter UI)",
applicationVersion: AppLocalizations.of(context).version.replaceAll(
"%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;
}
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 '../model.dart';
@ -6,7 +5,8 @@ import '../opaque.dart';
import '../widgets/messagelist.dart';
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 String conversationHandle;
@ -25,7 +25,7 @@ class _MessageViewState extends State<MessageView> {
@override
Widget build(BuildContext context) {
return Scaffold (
return Scaffold(
appBar: AppBar(
title: Text(widget.conversationHandle),
actions: [
@ -35,12 +35,14 @@ class _MessageViewState extends State<MessageView> {
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(),
);
}
void _pushConvoSettings(){}
void _pushConvoSettings() {}
void _sendMessage() {
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
@ -49,37 +51,38 @@ class _MessageViewState extends State<MessageView> {
}
Widget _buildComposeBox() {
return Container (
return Container(
color: Opaque.current().backgroundMainColor(),
height: 100,
child: Row(
children: <Widget>[
Expanded (
child:TextField(controller:ctrlrCompose)
),
Expanded(child: TextField(controller: ctrlrCompose)),
SizedBox(
width: 100,
height: 80,
child: Column(
children: <Widget>[
ElevatedButton(
child: Icon(Icons.send, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
), onPressed: _sendMessage,
child: Column(children: <Widget>[
ElevatedButton(
child:
Icon(Icons.send, color: Opaque.current().mainTextColor()),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Opaque.current().defaultButtonColor()),
),
Row (
children: <Widget>[
SizedBox(width:45, child:ElevatedButton(
child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor())
)),
SizedBox(width:45, child:ElevatedButton(
child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor())
)),
]
)
]
),
onPressed: _sendMessage,
),
Row(children: <Widget>[
SizedBox(
width: 45,
child: ElevatedButton(
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
Widget build(BuildContext context) {
return Scaffold (
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).profileName),
actions: [
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _testChangingContactInfo),
IconButton(icon: Icon(Icons.lock_open), onPressed: _modalUnlockProfiles,),
IconButton(icon: Icon(Icons.settings), onPressed: _pushGlobalSettings),
IconButton(
icon: Icon(Icons.bug_report_outlined),
onPressed: _testChangingContactInfo),
IconButton(
icon: Icon(Icons.lock_open),
onPressed: _modalUnlockProfiles,
),
IconButton(
icon: Icon(Icons.settings), onPressed: _pushGlobalSettings),
],
),
floatingActionButton: FloatingActionButton(
@ -39,75 +45,74 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
tooltip: AppLocalizations.of(context).addNewProfileBtn,
child: const Icon(Icons.add),
),
body: _buildProfileManager(),//_buildSuggestions(),
body: _buildProfileManager(), //_buildSuggestions(),
);
}
void _testChangingContactInfo() {
Provider.of<ProfileListState>(context, listen:false).notifyListeners();
Provider.of<ProfileListState>(context, listen: false).notifyListeners();
}
void _pushGlobalSettings() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider (
create: (_) => Provider.of<FlwtchState>(context),
child: GlobalSettingsView(),
);
},
)
);
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider(
create: (_) => Provider.of<FlwtchState>(context, listen: false),
child: GlobalSettingsView(),
);
},
));
}
void _pushAddEditProfile({onion: ""}) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return MultiProvider (
providers: [
ChangeNotifierProvider<ProfileInfoState>(create: (_) => ProfileInfoState(onion: onion),),
],
builder: (context, widget) => AddEditProfileView(),
);
},
)
);
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ProfileInfoState>(
create: (_) => ProfileInfoState(onion: onion),
),
],
builder: (context, widget) => AddEditProfileView(),
);
},
));
}
void _modalUnlockProfiles() {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200, // bespoke value courtesy of the [TextField] docs
color: Colors.pink[50],
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(AppLocalizations.of(context).enterProfilePassword),
TextField(
obscureText: true,
controller: ctrlrPassword,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context).password1Label,
return Container(
height: 200, // bespoke value courtesy of the [TextField] docs
color: Colors.pink[50],
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(AppLocalizations.of(context).enterProfilePassword),
TextField(
obscureText: true,
controller: ctrlrPassword,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context).password1Label,
),
),
),
ElevatedButton(
child: Text(AppLocalizations.of(context).unlock),
onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text);
Navigator.pop(context);
},
),
],
)
),
);
});
ElevatedButton(
child: Text(AppLocalizations.of(context).unlock),
onPressed: () {
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.LoadProfiles(ctrlrPassword.value.text);
Navigator.pop(context);
},
),
],
)),
);
});
}
Widget _buildProfileManager() {
@ -127,4 +132,4 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return ListView(children: divided);
}
}
}

View File

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

View File

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

View File

@ -1,11 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_app/settings.dart';
import 'package:provider/provider.dart';
import '../opaque.dart';
// Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
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 Function onPressed;
final Icon icon;
@ -18,33 +20,50 @@ class CwtchButtonTextField extends StatefulWidget {
class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
@override
Widget build(BuildContext context) {
return Consumer<OpaqueTheme>(builder: (context, theme, child) {
return Consumer<Settings>(builder: (context, theme, child) {
return TextField(
controller: widget.controller,
readOnly: true,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: widget.onPressed,
icon: widget.icon,
tooltip: widget.tooltip,
enableFeedback: true,
color: theme.current().mainTextColor(),
highlightColor: theme.current().defaultButtonColor(),
focusColor: theme.current().defaultButtonActiveColor(),
splashColor: theme.current().defaultButtonActiveColor(),
),
floatingLabelBehavior: FloatingLabelBehavior.never,
filled: true,
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)),
focusedErrorBorder: OutlineInputBorder(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)),
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()),
);
controller: widget.controller,
readOnly: true,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: widget.onPressed,
icon: widget.icon,
tooltip: widget.tooltip,
enableFeedback: true,
color: theme.current().mainTextColor(),
highlightColor: theme.current().defaultButtonColor(),
focusColor: theme.current().defaultButtonActiveColor(),
splashColor: theme.current().defaultButtonActiveColor(),
),
floatingLabelBehavior: FloatingLabelBehavior.never,
filled: true,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldBorderColor(), width: 3.0)),
focusedErrorBorder: OutlineInputBorder(
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)),
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,
height: 60,
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,))),
),
),
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(
contact.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
@ -32,7 +49,7 @@ class _ContactRowState extends State<ContactRow> {
subtitle: Text(contact.status),
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen:false);
var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.setState(() => flwtch.selectedConversation = contact.onion);
// case 2/3 handled by Double/TripleColumnView respectively
@ -47,7 +64,10 @@ class _ContactRowState extends State<ContactRow> {
MaterialPageRoute<void>(
builder: (BuildContext builderContext) {
return MultiProvider(
providers: [ChangeNotifierProvider<ProfileInfoState>(create: (_) => Provider.of<ProfileInfoState>(context)),],
providers: [
ChangeNotifierProvider<ProfileInfoState>(
create: (_) => Provider.of<ProfileInfoState>(context)),
],
child: MessageView(conversationHandle: handle),
);
},

View File

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

View File

@ -16,16 +16,23 @@ class MessageBubble extends StatefulWidget {
}
class _MessageBubbleState extends State<MessageBubble> {
String d="", ts="";
bool ack=false;
String d = "", ts = "";
bool ack = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("requesting message " + widget.messageIndex.toString());
Provider.of<FlwtchState>(context).cwtch.GetMessage(widget.profile.onion, widget.contactOnion, widget.messageIndex).then((jsonMessage){
print("got message: " + widget.messageIndex.toString() + ": " + jsonMessage);
Provider.of<FlwtchState>(context)
.cwtch
.GetMessage(
widget.profile.onion, widget.contactOnion, widget.messageIndex)
.then((jsonMessage) {
print("got message: " +
widget.messageIndex.toString() +
": " +
jsonMessage);
dynamic messageWrapper = jsonDecode(jsonMessage);
dynamic message = jsonDecode(messageWrapper['Message']);
setState(() {
@ -57,11 +64,12 @@ class _MessageBubbleState extends State<MessageBubble> {
),
subtitle: Row(
children: [
Text(""+widget.messageIndex.toString()),
ack ? Icon(Icons.check_circle_outline,
color: Opaque.current().mainTextColor()) : Icon(
Icons.hourglass_bottom_outlined,
color: Opaque.current().mainTextColor())
Text("" + widget.messageIndex.toString()),
ack
? Icon(Icons.check_circle_outline,
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 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
_MessageListState createState() => _MessageListState();
@ -30,18 +31,17 @@ class _MessageListState extends State<MessageList> {
}
return ProxyProvider0(
update: (_, __) => MessageCounter(conversationNumMessages),
child: ListView.builder(
itemCount: conversationNumMessages,
itemBuilder: (context, index) {
return MessageBubble(
profile: Provider.of<ProfileInfoState>(context),
contactOnion: widget.conversationHandle,
messageIndex: index,
);
},
)
);
update: (_, __) => MessageCounter(conversationNumMessages),
child: ListView.builder(
itemCount: conversationNumMessages,
itemBuilder: (context, index) {
return MessageBubble(
profile: Provider.of<ProfileInfoState>(context),
contactOnion: widget.conversationHandle,
messageIndex: index,
);
},
));
}
Future _updateMessageCount(BuildContext context) async {
@ -50,8 +50,14 @@ class _MessageListState extends State<MessageList> {
return;
}
Provider.of<FlwtchState>(context, listen: false).cwtch.NumMessages(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.conversationHandle).then((n) {
if (n != conversationNumMessages) setState(() => conversationNumMessages = n);
Provider.of<FlwtchState>(context, listen: false)
.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 {
MessageCounter(this.x);
int x = 0;
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../opaque.dart';
import '../settings.dart';
// Provides a styled Password Input Field for use in Form Widgets.
// 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> {
@override
Widget build(BuildContext context) {
return Consumer<OpaqueTheme>(builder: (context, theme, child) {
return Consumer<Settings>(builder: (context, theme, child) {
return TextFormField(
controller: widget.controller,
validator: widget.validator,
@ -25,18 +26,32 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(
errorStyle: TextStyle(
color: theme.current().textfieldErrorColor(),
fontWeight: FontWeight.bold,
),
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)),
focusedErrorBorder: OutlineInputBorder(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)),
errorStyle: TextStyle(
color: theme.current().textfieldErrorColor(),
fontWeight: FontWeight.bold,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldBorderColor(), width: 3.0)),
focusedErrorBorder: OutlineInputBorder(
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 '../model.dart';
import '../opaque.dart';
import '../settings.dart';
class ProfileRow extends StatefulWidget {
@override
@ -22,13 +23,30 @@ class _ProfileRowState extends State<ProfileRow> {
width: 60,
height: 60,
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(
icon: Icon(Icons.create, color: Provider.of<OpaqueTheme>(context).current().mainTextColor()),
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)
icon: Icon(Icons.create,
color: Provider.of<Settings>(context).current().mainTextColor()),
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(
profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
@ -36,7 +54,7 @@ class _ProfileRowState extends State<ProfileRow> {
subtitle: Text(profile.onion),
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen:false);
var flwtch = Provider.of<FlwtchState>(context, listen: false);
flwtch.cwtch.SelectProfile(profile.onion);
flwtch.setState(() {
flwtch.selectedProfile = profile;
@ -44,8 +62,12 @@ class _ProfileRowState extends State<ProfileRow> {
});
switch (flwtch.columns.length) {
case 1: _pushContactList(profile, false); break;
case 2: _pushContactList(profile, true); break;
case 1:
_pushContactList(profile, false);
break;
case 2:
_pushContactList(profile, true);
break;
} // case 3: handled by TripleColumnView
});
},
@ -59,28 +81,33 @@ class _ProfileRowState extends State<ProfileRow> {
return MultiProvider(
providers: [
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: ""}) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return MultiProvider (
providers: [
ChangeNotifierProvider<ProfileInfoState>(create: (_) => ProfileInfoState(onion: onion, nickname:displayName, imagePath: profileImage),),
],
builder: (context, widget) => AddEditProfileView(),
);
},
)
);
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ProfileInfoState>(
create: (_) => ProfileInfoState(
onion: onion, nickname: displayName, imagePath: profileImage),
),
],
builder: (context, widget) => AddEditProfileView(),
);
},
));
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../opaque.dart';
import '../settings.dart';
// Provides a styled Text Field for use in Form Widgets.
// 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> {
@override
Widget build(BuildContext context) {
return Consumer<OpaqueTheme>(builder: (context, theme, child) {
return Consumer<Settings>(builder: (context, theme, child) {
return TextFormField(
controller: widget.controller,
validator: widget.validator,
decoration: InputDecoration(
labelText: widget.labelText,
labelStyle: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
floatingLabelBehavior: FloatingLabelBehavior.never,
filled: true,
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)),
focusedErrorBorder: OutlineInputBorder(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)),
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()),
);
controller: widget.controller,
validator: widget.validator,
decoration: InputDecoration(
labelText: widget.labelText,
labelStyle: TextStyle(
color: theme.current().mainTextColor(),
backgroundColor: theme.current().textfieldBackgroundColor()),
floatingLabelBehavior: FloatingLabelBehavior.never,
filled: true,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide(
color: theme.current().textfieldBorderColor(), width: 3.0)),
focusedErrorBorder: OutlineInputBorder(
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)),
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) {
return Builder(
builder: (context2) => StreamBuilder<String>(
stream: Provider.of<FlwtchState>(context).appStatus.torStatus(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return Text(
snapshot.hasData ?
snapshot.data : AppLocalizations.of(context).loadingTor,
style: Theme
.of(context)
.textTheme
.headline4,
);
},
));
stream: Provider.of<FlwtchState>(context).appStatus.torStatus(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return Text(
snapshot.hasData
? snapshot.data
: AppLocalizations.of(context).loadingTor,
style: Theme.of(context).textTheme.headline4,
);
},
));
}
}