settings-pane #14
11
SPEC.md
11
SPEC.md
|
@ -44,13 +44,13 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
|||
- [X] Navigate to the Settings Pane (Settings Button in Action bar)
|
||||
|
||||
## Settings Pane
|
||||
|
||||
- [X] Save/Load
|
||||
- [X] Switch Dark / Light Theme
|
||||
- [ ] Switch Language
|
||||
- [X] Switch Language
|
||||
- [ ] Enable/Disable Experiments
|
||||
- [ ] Accessibility Settings (Zoom etc. - needs a deep dive into flutter)
|
||||
- [ ] Display Build & Version Info
|
||||
- [ ] Acknowledgements & Credits
|
||||
- [X] Display Build & Version Info
|
||||
- [X] Acknowledgements & Credits
|
||||
|
||||
## Profile Management Pane
|
||||
|
||||
|
@ -59,6 +59,9 @@ required - any new Cwtch work is beyond the scope of this initial spec.
|
|||
- [ ] Error Message When Attempting to Update Password with Wrong Old Password
|
||||
- [ ] Easy Transition from Unencrypted Profile -> Encrypted Profile
|
||||
- [ ] Delete a Profile
|
||||
- [ ] Dialog Acknowledgement
|
||||
- [ ] Require Old Password Gate
|
||||
- [ ] Async Checking of Password
|
||||
- [X] Copy Profile Onion Address
|
||||
|
||||
## Profile Pane (formally Contacts Pane)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}");
|
||||
|
|
|
@ -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,14 +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>();
|
||||
StartCwtch(Utf8.toUtf8(cwtchDir), cwtchDir.length, Utf8.toUtf8(""), 0);
|
||||
|
||||
final ut8CwtchDir = cwtchDir.toNativeUtf8();
|
||||
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"]);
|
||||
|
@ -105,117 +123,155 @@ 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) {
|
||||
Pointer<Utf8> result = GetAppbusEvent();
|
||||
String event = Utf8.fromUtf8(result);
|
||||
String event = result.toDartString();
|
||||
yield event;
|
||||
}
|
||||
}
|
||||
|
||||
// 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>();
|
||||
|
||||
SelectProfile(Utf8.toUtf8(onion), onion.length);
|
||||
final ut8Onion = onion.toNativeUtf8();
|
||||
SelectProfile(ut8Onion, ut8Onion.length);
|
||||
}
|
||||
|
||||
// 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>();
|
||||
CreateProfile(Utf8.toUtf8(nick), nick.length, Utf8.toUtf8(pass), pass.length);
|
||||
final utf8nick = nick.toNativeUtf8();
|
||||
final ut8pass = pass.toNativeUtf8();
|
||||
CreateProfile(utf8nick, utf8nick.length, ut8pass, ut8pass.length);
|
||||
}
|
||||
|
||||
// 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>();
|
||||
LoadProfiles(Utf8.toUtf8(pass), pass.length);
|
||||
final ut8pass = pass.toNativeUtf8();
|
||||
LoadProfiles(ut8pass, ut8pass.length);
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
Pointer<Utf8> result = ACNEvents();
|
||||
String event = Utf8.fromUtf8(result);
|
||||
String event = result.toDartString();
|
||||
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>();
|
||||
|
||||
Pointer<Utf8> result = ContactEvents();
|
||||
String event = Utf8.fromUtf8(result);
|
||||
String event = result.toDartString();
|
||||
return event;
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
Pointer<Utf8> jsonProfilesBytes = GetProfiles();
|
||||
String jsonProfiles = Utf8.fromUtf8(jsonProfilesBytes);
|
||||
String jsonProfiles = jsonProfilesBytes.toDartString();
|
||||
return jsonProfiles;
|
||||
}
|
||||
|
||||
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>();
|
||||
Pointer<Utf8> jsonContactBytes = GetContacts(Utf8.toUtf8(onion), onion.length);
|
||||
String jsonContacts = Utf8.fromUtf8(jsonContactBytes);
|
||||
final utf8onion = onion.toNativeUtf8();
|
||||
Pointer<Utf8> jsonContactBytes = GetContacts(utf8onion, utf8onion.length);
|
||||
String jsonContacts = jsonContactBytes.toDartString();
|
||||
return jsonContacts;
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
int num = NumMessages(Utf8.toUtf8(profile), profile.length, Utf8.toUtf8(handle), handle.length);
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
final utf8handle = handle.toNativeUtf8();
|
||||
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>();
|
||||
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessage(Utf8.toUtf8(profile), profile.length, Utf8.toUtf8(handle), handle.length, index);
|
||||
String jsonMessage = Utf8.fromUtf8(jsonMessageBytes);
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
final utf8handle = handle.toNativeUtf8();
|
||||
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>();
|
||||
|
||||
Pointer<Utf8> jsonMessagesBytes = GetMessages(Utf8.toUtf8(profile), profile.length, Utf8.toUtf8(handle), handle.length, start, end);
|
||||
String jsonMessages = Utf8.fromUtf8(jsonMessagesBytes);
|
||||
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);
|
||||
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>();
|
||||
SendAppBusEvent(Utf8.toUtf8(onion), onion.length, Utf8.toUtf8(json), json.length);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter_app/cwtch/ffi.dart';
|
||||
import 'package:flutter_app/cwtch/gomobile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app/settings.dart';
|
||||
import 'package:flutter_app/views/triplecolview.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'cwtch/cwtch.dart';
|
||||
|
@ -12,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 {
|
||||
|
@ -36,8 +39,9 @@ class FlwtchState extends State<Flwtch> {
|
|||
initState() {
|
||||
super.initState();
|
||||
cwtchInit = false;
|
||||
|
||||
profs = ProfileListState();
|
||||
var cwtchNotifier = new CwtchNotifier(profs);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, GlobalSettings);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
cwtch = CwtchGomobile(cwtchNotifier);
|
||||
|
@ -54,25 +58,28 @@ class FlwtchState extends State<Flwtch> {
|
|||
appStatus = AppModel(cwtch: cwtch);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
final newTextTheme = Theme.of(context).textTheme.apply(
|
||||
bodyColor: Opaque.current().mainTextColor(),
|
||||
displayColor: Opaque.current().mainTextColor(),
|
||||
);
|
||||
|
||||
return MultiProvider(
|
||||
providers: [getFlwtchStateProvider(), getProfileListProvider(), getOpaqueProvider()],
|
||||
providers: [
|
||||
getFlwtchStateProvider(),
|
||||
getProfileListProvider(),
|
||||
getSettingsProvider()
|
||||
],
|
||||
builder: (context, widget) {
|
||||
return Consumer<OpaqueTheme>(
|
||||
Provider.of<Settings>(context).initPackageInfo();
|
||||
return Consumer<Settings>(
|
||||
builder: (context, opaque, child) => MaterialApp(
|
||||
locale: Locale("en",''),
|
||||
locale: Provider.of<Settings>(context).locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
title: 'Cwtch',
|
||||
|
@ -83,11 +90,47 @@ class FlwtchState extends State<Flwtch> {
|
|||
canvasColor: opaque.current().backgroundPaneColor(),
|
||||
accentColor: opaque.current().defaultButtonColor(),
|
||||
buttonColor: opaque.current().defaultButtonColor(),
|
||||
textTheme: newTextTheme,
|
||||
backgroundColor: opaque.current().backgroundMainColor(),
|
||||
iconTheme: IconThemeData(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
cardColor: opaque.current().backgroundMainColor(),
|
||||
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())),
|
||||
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())),
|
||||
),
|
||||
// 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(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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});
|
||||
|
|
1655
lib/opaque.dart
1655
lib/opaque.dart
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,71 @@
|
|||
import 'dart:ui';
|
||||
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;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
switchLocale(Locale newLocale) {
|
||||
locale = newLocale;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
],
|
||||
));
|
||||
|
|
|
@ -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);
|
||||
|
@ -29,6 +30,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
final ctrlrPass2 = TextEditingController(text: "");
|
||||
final ctrlrOnion = TextEditingController(text: "");
|
||||
bool usePassword;
|
||||
bool deleted;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -45,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(),
|
||||
);
|
||||
|
@ -61,178 +65,322 @@ 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 SingleChildScrollView(
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
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,
|
||||
))),
|
||||
return Consumer<Settings>(builder: (context, theme, child) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints viewportConstraints) {
|
||||
return Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
child: SingleChildScrollView(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
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,
|
||||
),
|
||||
)
|
||||
])),
|
||||
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(
|
||||
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,
|
||||
),
|
||||
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: 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,
|
||||
),
|
||||
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),
|
||||
)
|
||||
]))
|
||||
]))))));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -243,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 {
|
||||
|
@ -259,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
|
||||
|
@ -270,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();
|
||||
}
|
||||
|
@ -286,3 +453,53 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
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()),
|
||||
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = TextButton(
|
||||
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))),
|
||||
child: Text(AppLocalizations.of(context).deleteProfileConfirmBtn),
|
||||
onPressed: () {
|
||||
// TODO Actually Delete the Peer
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(AppLocalizations.of(context).deleteProfileConfirmBtn),
|
||||
actions: [
|
||||
cancelButton,
|
||||
continueButton,
|
||||
],
|
||||
);
|
||||
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app/opaque.dart';
|
||||
import 'package:flutter_app/settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../main.dart';
|
||||
|
||||
class GlobalSettingsView extends StatefulWidget {
|
||||
@override
|
||||
_GlobalSettingsViewState createState() => _GlobalSettingsViewState();
|
||||
}
|
||||
|
||||
class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||
final myController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -28,40 +31,106 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
}
|
||||
|
||||
Widget _buildSettingsList() {
|
||||
return Consumer<OpaqueTheme>(
|
||||
builder: (context, theme, child) {
|
||||
return Center(child: Column(
|
||||
children: [
|
||||
Text(AppLocalizations.of(context).settingLanguage),
|
||||
TextField(
|
||||
controller: myController,
|
||||
onChanged: (text) {
|
||||
print("First text field: $text");
|
||||
},
|
||||
),
|
||||
Text(AppLocalizations.of(context).settingInterfaceZoom),
|
||||
SwitchListTile(
|
||||
title: Text('Theme',
|
||||
style: TextStyle(color: theme.current().mainTextColor())),
|
||||
value: theme.current() == Opaque.light,
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
theme.setLight();
|
||||
} else {
|
||||
theme.setDark();
|
||||
}
|
||||
},
|
||||
secondary: Icon(Icons.lightbulb_outline,
|
||||
color: theme.current().mainTextColor()),
|
||||
),
|
||||
Text(AppLocalizations.of(context).experimentsEnabled),
|
||||
Text("Text magnification reference"),//dev
|
||||
Text("Acknowledgements"),//todo
|
||||
Text(AppLocalizations.of(context).version),
|
||||
Text(AppLocalizations.of(context).builddate),
|
||||
]
|
||||
));
|
||||
}
|
||||
);
|
||||
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()),
|
||||
trailing: DropdownButton(
|
||||
value: Provider.of<Settings>(context).locale.languageCode,
|
||||
onChanged: (String newValue) {
|
||||
setState(() {
|
||||
var settings =
|
||||
Provider.of<Settings>(context, listen: false);
|
||||
settings.switchLocale(Locale(newValue, ''));
|
||||
saveSettings(context);
|
||||
});
|
||||
},
|
||||
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())),
|
||||
value: theme.current() == Opaque.light,
|
||||
onChanged: (bool value) {
|
||||
if (value) {
|
||||
theme.setLight();
|
||||
} else {
|
||||
theme.setDark();
|
||||
}
|
||||
|
||||
// Save Settings...
|
||||
saveSettings(context);
|
||||
},
|
||||
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',
|
||||
),
|
||||
]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String constructVersionString(PackageInfo pinfo) {
|
||||
if (pinfo == null) {
|
||||
return "";
|
||||
}
|
||||
return pinfo.version + "." + pinfo.buildNumber;
|
||||
}
|
||||
|
||||
String getLanguageFull(context, String languageCode) {
|
||||
if (languageCode == "en") {
|
||||
return AppLocalizations.of(context).localeEn;
|
||||
}
|
||||
if (languageCode == "es") {
|
||||
return AppLocalizations.of(context).localeEs;
|
||||
}
|
||||
if (languageCode == "fr") {
|
||||
return AppLocalizations.of(context).localeFr;
|
||||
}
|
||||
if (languageCode == "pt") {
|
||||
return AppLocalizations.of(context).localePt;
|
||||
}
|
||||
if (languageCode == "de") {
|
||||
return AppLocalizations.of(context).localeDe;
|
||||
}
|
||||
if (languageCode == "it") {
|
||||
return AppLocalizations.of(context).localeIt;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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()))),
|
||||
])
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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...")])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
124
pubspec.lock
124
pubspec.lock
|
@ -21,42 +21,42 @@ packages:
|
|||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.5.0-nullsafety.3"
|
||||
version: "2.5.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.3"
|
||||
version: "2.1.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0-nullsafety.5"
|
||||
version: "1.1.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0-nullsafety.3"
|
||||
version: "1.2.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0-nullsafety.3"
|
||||
version: "1.1.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0-nullsafety.5"
|
||||
version: "1.15.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -84,21 +84,21 @@ packages:
|
|||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0-nullsafety.3"
|
||||
version: "1.2.0"
|
||||
ffi:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
version: "1.0.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.0-nullsafety.4"
|
||||
version: "6.1.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -115,12 +115,17 @@ packages:
|
|||
name: flutter_lokalise
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
version: "0.1.3"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
freezed_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -134,21 +139,28 @@ packages:
|
|||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.2"
|
||||
version: "0.13.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "4.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.17.0-nullsafety.2"
|
||||
version: "0.17.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -169,14 +181,14 @@ packages:
|
|||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.10-nullsafety.3"
|
||||
version: "0.12.10"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0-nullsafety.6"
|
||||
version: "1.3.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -184,48 +196,90 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.4"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_info_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_info_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_info_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_info_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0-nullsafety.3"
|
||||
version: "1.8.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.27"
|
||||
version: "2.0.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.1+2"
|
||||
version: "2.0.0"
|
||||
path_provider_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.4+8"
|
||||
version: "2.0.0"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "2.0.1"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.4+3"
|
||||
version: "2.0.0"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -246,14 +300,14 @@ packages:
|
|||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "2.0.0"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0-nullsafety.4"
|
||||
version: "4.1.0"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -279,28 +333,28 @@ packages:
|
|||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0-nullsafety.4"
|
||||
version: "1.8.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0-nullsafety.6"
|
||||
version: "1.10.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.3"
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0-nullsafety.3"
|
||||
version: "1.1.0"
|
||||
string_unescape:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -314,42 +368,42 @@ packages:
|
|||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0-nullsafety.3"
|
||||
version: "1.2.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.19-nullsafety.6"
|
||||
version: "0.2.19"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0-nullsafety.5"
|
||||
version: "1.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0-nullsafety.5"
|
||||
version: "2.1.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.4"
|
||||
version: "2.0.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
version: "0.2.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -358,5 +412,5 @@ packages:
|
|||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=2.12.0-0.0 <3.0.0"
|
||||
flutter: ">=1.16.0"
|
||||
dart: ">=2.12.0-259.9.beta <3.0.0"
|
||||
flutter: ">=1.20.0"
|
||||
|
|
|
@ -24,6 +24,7 @@ dependencies:
|
|||
flutter:
|
||||
sdk: flutter
|
||||
provider: "4.3.2+3"
|
||||
package_info_plus: ^1.0.0
|
||||
#intl_translation: any
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
@ -31,8 +32,9 @@ dependencies:
|
|||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.0
|
||||
ffi: ^0.1.3
|
||||
path_provider: ^1.6.27
|
||||
ffi: ^1.0.0
|
||||
path_provider: ^2.0.0
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -75,6 +77,7 @@ flutter:
|
|||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
assets:
|
||||
- assets/
|
||||
- assets/profiles/
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
|
|
Loading…
Reference in New Issue