slowly massaging things into new state model

This commit is contained in:
erinn 2021-02-03 03:22:25 -08:00
parent a5135365dc
commit 0fea363e4d
12 changed files with 291 additions and 162 deletions

View File

@ -21,6 +21,8 @@ import cwtch.Cwtch
import io.flutter.plugin.common.EventChannel
import kotlin.concurrent.thread
import org.json.JSONObject
class MainActivity: FlutterActivity() {
// Channel to get app info
@ -71,15 +73,14 @@ class MainActivity: FlutterActivity() {
Log.i("MainActivity.kt", "got event chan: " + eventbus_chan + " launching corouting...")
GlobalScope.launch(Dispatchers.IO) {
while(true) {
//val jsonEvent = Cwtch.getAppBusEvent()
// Log.i("MainActivity.kt", "got appbusEvent: " + jsonEvent)
// launch(Dispatchers.Main) {
// //eventbus_chan.invokeMethod("AppbusEvent", jsonEvent)
//}
val evt = AppbusEvent(Cwtch.getAppBusEvent())
Log.i("MainActivity.kt", "got appbusEvent: " + evt)
launch(Dispatchers.Main) {
//todo: this elides evt.EventID which may be needed at some point?
eventbus_chan.invokeMethod(evt.EventType, evt.Data)
}
}
}
}
"SelectProfile" -> {
val onion = (call.argument("profile") as? String) ?: "";
@ -129,4 +130,24 @@ class MainActivity: FlutterActivity() {
else -> result.notImplemented()
}
}
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
// for reference:
//
// class Response(json: String) : JSONObject(json) {
// val type: String? = this.optString("type")
// val data = this.optJSONArray("data")
// ?.let { 0.until(it.length()).map { i -> it.optJSONObject(i) } } // returns an array of JSONObject
// ?.map { Foo(it.toString()) } // transforms each JSONObject of the array into Foo
// }
//
// class Foo(json: String) : JSONObject(json) {
// val id = this.optInt("id")
// val title: String? = this.optString("title")
// }
class AppbusEvent(json: String) : JSONObject(json) {
val EventType = this.optString("EventType")
val EventID = this.optString("EventID")
val Data = this.optString("Data")
}
}

Binary file not shown.

View File

@ -1,9 +1,12 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_app/model.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'package:path/path.dart' as path;
import 'package:provider/provider.dart';
import 'cwtch.dart';
@ -26,14 +29,16 @@ class CwtchGomobile implements Cwtch {
Future<String> androidLibraryDir;
Future<Directory> androidHomeDirectory;
ProfileListState profileCN;
CwtchGomobile() {
CwtchGomobile(ProfileListState profs) {
print("gomobile.dart: CwtchGomobile()");
profileCN = profs;
androidHomeDirectory = getApplicationDocumentsDirectory();
androidLibraryDir = appInfoPlatform.invokeMethod('getNativeLibDir');
// final appbusEventChannel = MethodChannel(appbusEventChannelName);
// appbusEventChannel.setMethodCallHandler(this._handleAppbusEvent);
final appbusEventChannel = MethodChannel(appbusEventChannelName);
appbusEventChannel.setMethodCallHandler(this._handleAppbusEvent);
}
Future<void> Start() async {
@ -46,7 +51,13 @@ class CwtchGomobile implements Cwtch {
Future<void> _handleAppbusEvent(MethodCall call) async {
final String json = call.arguments;
print("appbus event: ${call.method} $json");
var obj = jsonDecode(json);
switch (call.method) {
case "NewPeer":
profileCN.add(ProfileInfoState(onion: obj["Identity"], nickname: obj["ProfileName"], imagePath: obj["Path"]));
break;
default: print("unhandled gomobile appbus event: $call");
}
}
void SelectProfile(String onion) {

View File

@ -1,6 +1,4 @@
import 'dart:collection';
import 'dart:convert';
import 'package:flutter_app/cwtch/ffi.dart';
import 'package:flutter_app/cwtch/gomobile.dart';
import 'package:flutter/material.dart';
@ -24,112 +22,70 @@ class Flwtch extends StatefulWidget {
class FlwtchState extends State<Flwtch> {
final TextStyle biggerFont = const TextStyle(fontSize: 18);
// mergenotes: dan's
Cwtch cwtch;
bool cwtchInit = false;
// mergenotes: ui stuff
ProfileModel selectedProfile;
ProfileInfoState selectedProfile;
String selectedConversation = "";
var columns = [1]; // default or 'single column' mode
//var columns = [1, 1, 2];
AppModel appStatus;
HashMap<String, ProfileModel> profiles;
ProfileListState profs;
@override
initState() {
super.initState();
cwtchInit = false;
profiles = new HashMap<String, ProfileModel>();
print("FlwtchState.initState()");
profs = ProfileListState();
if (Platform.isAndroid) {
cwtch = CwtchGomobile();
cwtch = CwtchGomobile(profs);
} else {
cwtch = CwtchFfi();
}
cwtch.Start().then((val) {
setState(() {
cwtchInit = true;
loadProfiles();
});
});
appStatus = AppModel(cwtch: cwtch);
// Timing issue? Start may not have inited cwtch yet when we ask for getProfiles...
}
void loadProfiles() {
cwtch.GetProfiles().then((profilesJson) {
setState(() {
jsonDecode(profilesJson).forEach((profile) {
ProfileModel profile1 = new ProfileModel();
profile1.onion = profile['onion'];
profile1.nickname = profile['name'];
profile1.creationDate = "4 jan 2020";
profile1.contacts = new HashMap<String, ContactModel>();
profile1.imagePath = profile['imagePath'];
profiles.putIfAbsent(profile1.onion, () => profile1);
});
});
});
}
ChangeNotifierProvider<OpaqueTheme> getOpaqueProvider() {
return ChangeNotifierProvider(create: (context) => OpaqueTheme(Opaque.dark));
}
Provider<FlwtchState> getFlwtchStateProvider() {
return Provider<FlwtchState>(
create: (_) => this,
);
}
ChangeNotifierProvider<OpaqueTheme> getOpaqueProvider() => ChangeNotifierProvider(create: (context) => OpaqueTheme(Opaque.dark));
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(),
);
bodyColor: Opaque.current().mainTextColor(),
displayColor: Opaque.current().mainTextColor(),
);
print("FlwtchState.build() cwtchInit: $cwtchInit");
return MultiProvider(
providers: [getFlwtchStateProvider(), getOpaqueProvider()],
builder: (context, widget) {
return MaterialApp(
title: 'Cwtch',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
primarySwatch: Colors.red,
primaryColor: Provider.of<OpaqueTheme>(context)
.current()
.backgroundMainColor(),
canvasColor: Provider.of<OpaqueTheme>(context)
.current()
.backgroundPaneColor(),
accentColor: Provider.of<OpaqueTheme>(context)
.current()
.defaultButtonColor(),
buttonColor: Provider.of<OpaqueTheme>(context)
.current()
.defaultButtonColor(),
textTheme: newTextTheme,
),
// 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(),
);},
providers: [getFlwtchStateProvider(), getProfileListProvider(), getOpaqueProvider()],
builder: (context, widget) {
return Consumer<OpaqueTheme>(
builder: (context, opaque, child) => MaterialApp(
title: 'Cwtch',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
primarySwatch: Colors.red,
primaryColor: opaque.current().backgroundMainColor(),
canvasColor: opaque.current().backgroundPaneColor(),
accentColor: opaque.current().defaultButtonColor(),
buttonColor: opaque.current().defaultButtonColor(),
textTheme: newTextTheme,
),
// 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(),
),
);
},
);
}
}

View File

@ -1,10 +1,12 @@
import 'dart:convert';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/cupertino.dart';
import 'dart:async';
import 'dart:collection';
import 'cwtch/cwtch.dart';
import 'main.dart';
////////////////////
/// UI State ///
@ -53,6 +55,116 @@ class ChatMessage {
};
}
///////////////////
/// Providers ///
///////////////////
class ProfileListState extends ChangeNotifier {
List<ProfileInfoState> _onions = [];
int get num => _onions.length;
void addAll(Iterable<ProfileInfoState> newOnions) {
_onions.addAll(newOnions);
notifyListeners();
}
void add(ProfileInfoState newOnion) {
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
}
class ContactListState extends ChangeNotifier {
List<ContactInfoState> _onions = [];
int get num => _onions.length;
void addAll(Iterable<ContactInfoState> newOnions) {
_onions.addAll(newOnions);
notifyListeners();
}
void add(ContactInfoState newOnion) {
_onions.add(newOnion);
notifyListeners();
}
void updateUnreadMessages(String forOnion, int newVal) {
_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
}
class ProfileInfoState extends ChangeNotifier {
final String onion;
String _nickname = "";
String _imagePath = "";
int _unreadMessages = 0;
ProfileInfoState({this.onion, nickname = "", imagePath = "", unreadMessages = 0,}){
this._nickname = nickname;
this._imagePath = imagePath;
this._unreadMessages = unreadMessages;
}
String get nickname => this._nickname;
set nickname(String newValue) {
this.nickname = newValue;
notifyListeners();
}
String get imagePath => this._imagePath;
set imagePath(String newVal) {
this._imagePath = newVal;
notifyListeners();
}
int get unreadMessages => this._unreadMessages;
set unreadMessages(int newVal) {
this._unreadMessages = newVal;
notifyListeners();
}
}
class ContactInfoState extends ChangeNotifier {
final String profileOnion;
final String onion;
String _nickname;
bool _isGroup;
bool _isInvitation;
bool _isBlocked;
String _status;
String _imagePath;
int _unreadMessages = 0;
ContactInfoState({this.profileOnion, this.onion, nickname = "", isGroup = false, isInvitation = false, isBlocked = false, status = "", imagePath = "",}) {
this._nickname = nickname;
this._isGroup = isGroup;
this._isInvitation = isInvitation;
this._isBlocked = isBlocked;
this._status = status;
this._imagePath = imagePath;
}
get nickname => this._nickname;
set nickname(String newVal) {
this._nickname = newVal;
notifyListeners();
}
get unreadMessages => this._unreadMessages;
set unreadMessages(int newVal) {
this._unreadMessages = newVal;
notifyListeners();
}
}
/////////////
/// ACN ///
/////////////
@ -70,6 +182,7 @@ class AppModel {
print(event);
yield event;
} else {
print("TEST TEST FAIL TEST FAIL 123");
await Future.delayed(Duration(seconds: 1));
}
}
@ -81,6 +194,7 @@ class AppModel {
if (event != "") {
yield event;
} else {
print("TOR TEST TEST FAIL TEST FAIL 123");
await Future.delayed(Duration(seconds: 1));
}
}

View File

@ -10,7 +10,7 @@ import '../model.dart';
class ContactsView extends StatefulWidget {
const ContactsView({Key key, this.profile}) : super(key: key);
final ProfileModel profile;
final ProfileInfoState profile;
@override
_ContactsViewState createState() => _ContactsViewState();

View File

@ -12,7 +12,7 @@ import '../widgets/messagelist.dart';
class MessageView extends StatefulWidget {
const MessageView({Key key, this.profile, this.conversationHandle}) : super(key: key);
final ProfileModel profile;
final ProfileInfoState profile;
final String conversationHandle;
@override

View File

@ -1,14 +1,9 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_app/widgets/profilerow.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../model.dart';
import 'dart:collection';
import '../opaque.dart';
import 'addeditprofileview.dart';
import 'contactsview.dart';
import 'doublecolview.dart';
import 'globalsettingsview.dart';
class ProfileMgrView extends StatefulWidget {
@ -29,7 +24,6 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
@override
Widget build(BuildContext context) {
return Scaffold (
appBar: AppBar(
title: Text('Profiles'),
@ -47,19 +41,6 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
);
}
void _pushContactList(ProfileModel profile, bool includeDoublePane) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider(
create: (_) => Provider.of<FlwtchState>(context),
child: includeDoublePane ? DoubleColumnView() : ContactsView(profile:profile),
);
},
),
);
}
void _pushGlobalSettings() {
Navigator.of(context).push(
MaterialPageRoute<void>(
@ -75,14 +56,14 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
void _pushAddEditProfile({onion: ""}) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider (
create: (_) => Provider.of<FlwtchState>(context, listen: false),
child: AddEditProfileView(profileOnion: onion),
);
},
)
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider (
create: (_) => Provider.of<FlwtchState>(context, listen: false),
child: AddEditProfileView(profileOnion: onion),
);
},
)
);
}
@ -110,12 +91,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
ElevatedButton(
child: const Text('Unlock'),
onPressed: () {
Provider
.of<FlwtchState>(context, listen: false)
.cwtch
.LoadProfiles(ctrlrPassword.value.text);
Provider.of<FlwtchState>(context, listen: false)
.loadProfiles();
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text);
Navigator.pop(context);
},
),
@ -126,42 +102,12 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
});
}
String getNick(String profile, String contact) {
return contact == profile ? "me" : Provider.of<FlwtchState>(context).profiles[profile].contacts[contact].nickname;
}
Widget _buildProfileManager() {
final tiles = Provider.of<FlwtchState>(context).profiles.values.map(
(ProfileModel profile) {
return ListTile(
leading: SizedBox(
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,))),
),
) ,
trailing: IconButton(icon: Icon(Icons.create, color: Opaque.current().mainTextColor()), onPressed: () { _pushAddEditProfile(onion: profile.onion); }),//(nb: Icons.create is a pencil and we use it for "edit", not create)
title: Text(
profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
),
subtitle: Text(profile.onion),
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen:false);
flwtch.cwtch.SelectProfile(profile.onion);
flwtch.setState(() {
flwtch.selectedProfile = profile;
flwtch.selectedConversation = "";
});
switch (flwtch.columns.length) {
case 1: _pushContactList(profile, false); break;
case 2: _pushContactList(profile, true); break;
} // case 3: handled by TripleColumnView
});
},
final tiles = Provider.of<ProfileListState>(context).onions.map(
(ProfileInfoState profile) {
return ChangeNotifierProvider<ProfileInfoState>(
create: (context) => profile,
builder: (context, child) => ProfileRow(profile),
);
},
);
@ -173,5 +119,4 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return ListView(children: divided);
}
}
}

View File

@ -7,7 +7,7 @@ import '../opaque.dart';
class MessageBubble extends StatefulWidget {
MessageBubble({Key key, this.profile, this.contactOnion, this.messageIndex});
final ProfileModel profile;
final ProfileInfoState profile;
final String contactOnion;
final int messageIndex;

View File

@ -7,7 +7,7 @@ import '../model.dart';
import 'messagebubble.dart';
class MessageList extends StatefulWidget {
final ProfileModel profile;
final ProfileInfoState profile;
final String conversationHandle;
const MessageList({Key key, this.profile, this.conversationHandle}) : super(key: key);

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:flutter_app/views/addeditprofileview.dart';
import 'package:flutter_app/views/contactsview.dart';
import 'package:flutter_app/views/doublecolview.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../model.dart';
import '../opaque.dart';
class ProfileRow extends StatefulWidget {
final ProfileInfoState profile;
ProfileRow(this.profile);
@override
_ProfileRowState createState() => _ProfileRowState();
}
class _ProfileRowState extends State<ProfileRow> {
@override
Widget build(BuildContext context) {
return ListTile(
leading: SizedBox(
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/" + widget.profile.imagePath), width:50,height:50,))),
),
) ,
trailing: IconButton(
icon: Icon(Icons.create, color: Provider.of<OpaqueTheme>(context).current().mainTextColor()),
onPressed: () { _pushAddEditProfile(onion: widget.profile.onion); },
),//(nb: Icons.create is a pencil and we use it for "edit", not create)
title: Text(
widget.profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
),
subtitle: Text(widget.profile.onion),
onTap: () {
setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen:false);
flwtch.cwtch.SelectProfile(widget.profile.onion);
flwtch.setState(() {
flwtch.selectedProfile = widget.profile;
flwtch.selectedConversation = "";
});
switch (flwtch.columns.length) {
case 1: _pushContactList(widget.profile, false); break;
case 2: _pushContactList(widget.profile, true); break;
} // case 3: handled by TripleColumnView
});
},
);
}
void _pushContactList(ProfileInfoState profile, bool includeDoublePane) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider(
create: (_) => Provider.of<FlwtchState>(context),
child: includeDoublePane ? DoubleColumnView() : ContactsView(profile:profile),
);
},
),
);
}
void _pushAddEditProfile({onion: ""}) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Provider (
create: (_) => Provider.of<FlwtchState>(context, listen: false),
child: AddEditProfileView(profileOnion: onion),
);
},
)
);
}
}

View File

@ -13,7 +13,7 @@ import 'package:flutter_app/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
await tester.pumpWidget(Flwtch());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);