dual pane wiring
This commit is contained in:
parent
eb12f57135
commit
82ee06d840
|
@ -136,7 +136,7 @@ class MainActivity: FlutterActivity() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
Log.i("MainActivity.kt", "onResume")
|
Log.i("MainActivity.kt", "onResume")
|
||||||
if (myReceiver == null) {
|
if (myReceiver == null) {
|
||||||
Log.i("MainActivity.kt", "onResume registering localbroadcastreceiver")
|
Log.i("MainActivity.kt", "onResume registering local broadcast receiver / event bus forwarder")
|
||||||
val mc = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
|
val mc = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
|
||||||
val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
|
val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
|
||||||
myReceiver = MyBroadcastReceiver(mc)
|
myReceiver = MyBroadcastReceiver(mc)
|
||||||
|
@ -163,7 +163,7 @@ class MainActivity: FlutterActivity() {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
Log.i("MainActivity.kt", "onDestroy")
|
Log.i("MainActivity.kt", "onDestroy - cancelling all WORKER_TAG and pruning old work")
|
||||||
WorkManager.getInstance(this).cancelAllWorkByTag(WORKER_TAG)
|
WorkManager.getInstance(this).cancelAllWorkByTag(WORKER_TAG)
|
||||||
WorkManager.getInstance(this).pruneWork()
|
WorkManager.getInstance(this).pruneWork()
|
||||||
}
|
}
|
||||||
|
@ -188,6 +188,8 @@ class MainActivity: FlutterActivity() {
|
||||||
val Data = this.optString("Data")
|
val Data = this.optString("Data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MainActivity.MyBroadcastReceiver receives events from the Cwtch service via im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS Android local broadcast intents
|
||||||
|
// then it forwards them to the flutter ui engine using the CWTCH_EVENTBUS methodchannel
|
||||||
class MyBroadcastReceiver(mc: MethodChannel) : BroadcastReceiver() {
|
class MyBroadcastReceiver(mc: MethodChannel) : BroadcastReceiver() {
|
||||||
val eventBus: MethodChannel = mc
|
val eventBus: MethodChannel = mc
|
||||||
|
|
||||||
|
@ -198,5 +200,4 @@ class MainActivity: FlutterActivity() {
|
||||||
eventBus.invokeMethod(evtType, evtData)
|
eventBus.invokeMethod(evtType, evtData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,9 @@ class CwtchNotifier {
|
||||||
break;
|
break;
|
||||||
case "NewMessageFromPeer":
|
case "NewMessageFromPeer":
|
||||||
notificationManager.notify("New Message From Peer!");
|
notificationManager.notify("New Message From Peer!");
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["RemotePeer"]) {
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
||||||
|
}
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||||
break;
|
break;
|
||||||
|
@ -122,7 +124,9 @@ class CwtchNotifier {
|
||||||
case "NewMessageFromGroup":
|
case "NewMessageFromGroup":
|
||||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||||
//not from me
|
//not from me
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||||
|
}
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -45,10 +45,6 @@ class Flwtch extends StatefulWidget {
|
||||||
class FlwtchState extends State<Flwtch> {
|
class FlwtchState extends State<Flwtch> {
|
||||||
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
||||||
late Cwtch cwtch;
|
late Cwtch cwtch;
|
||||||
late ProfileInfoState selectedProfile;
|
|
||||||
String selectedConversation = "";
|
|
||||||
var columns = [1]; // default or 'single column' mode
|
|
||||||
//var columns = [1, 1, 2];
|
|
||||||
late ProfileListState profs;
|
late ProfileListState profs;
|
||||||
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
|
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
|
||||||
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdown');
|
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdown');
|
||||||
|
@ -108,7 +104,7 @@ class FlwtchState extends State<Flwtch> {
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
title: 'Cwtch',
|
title: 'Cwtch',
|
||||||
theme: mkThemeData(settings),
|
theme: mkThemeData(settings),
|
||||||
home: appState.cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(),
|
home: appState.cwtchInit == true ? ShiftRightFixer(child: ProfileMgrView()) : SplashView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -128,24 +124,38 @@ class FlwtchState extends State<Flwtch> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt
|
||||||
|
// coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
|
||||||
Future<void> _externalNotificationClicked(MethodCall call) async {
|
Future<void> _externalNotificationClicked(MethodCall call) async {
|
||||||
var args = jsonDecode(call.arguments);
|
var args = jsonDecode(call.arguments);
|
||||||
var profile = profs.getProfile(args["ProfileOnion"])!;
|
var profile = profs.getProfile(args["ProfileOnion"])!;
|
||||||
var contact = profile.contactList.getContact(args["RemotePeer"])!;
|
var contact = profile.contactList.getContact(args["RemotePeer"])!;
|
||||||
contact.unreadMessages = 0;
|
contact.unreadMessages = 0;
|
||||||
navKey.currentState?.push(
|
|
||||||
MaterialPageRoute<void>(
|
// single pane mode pushes; double pane mode reads AppState.selectedProfile/Conversation
|
||||||
builder: (BuildContext builderContext) {
|
var isLandscape = Provider.of<AppState>(navKey.currentContext!, listen: false).isLandscape(navKey.currentContext!);
|
||||||
return MultiProvider(
|
if (Provider.of<Settings>(navKey.currentContext!, listen: false).uiColumns(isLandscape).length == 1) {
|
||||||
providers: [
|
if (navKey.currentContext?.findAncestorWidgetOfExactType<MessageView>() != null) {
|
||||||
ChangeNotifierProvider.value(value: profile),
|
print("messageview already open; popping before pushing replacement");
|
||||||
ChangeNotifierProvider.value(value: contact),
|
navKey.currentState?.pop();
|
||||||
],
|
}
|
||||||
builder: (context, child) => MessageView(),
|
navKey.currentState?.push(
|
||||||
);
|
MaterialPageRoute<void>(
|
||||||
},
|
builder: (BuildContext builderContext) {
|
||||||
),
|
return MultiProvider(
|
||||||
);
|
providers: [
|
||||||
|
ChangeNotifierProvider.value(value: profile),
|
||||||
|
ChangeNotifierProvider.value(value: contact),
|
||||||
|
],
|
||||||
|
builder: (context, child) => MessageView(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else { //dual pane
|
||||||
|
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedProfile = args["ProfileOnion"];
|
||||||
|
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = args["RemotePeer"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:glob/glob.dart';
|
import 'package:glob/glob.dart';
|
||||||
import 'package:glob/list_local_fs.dart';
|
//import 'package:glob/list_local_fs.dart';
|
||||||
|
|
||||||
var globalSettings = Settings(Locale("en", ''), OpaqueDark());
|
var globalSettings = Settings(Locale("en", ''), OpaqueDark());
|
||||||
var globalErrorHandler = ErrorHandler();
|
var globalErrorHandler = ErrorHandler();
|
||||||
|
|
|
@ -64,6 +64,8 @@ class ProfileListState extends ChangeNotifier {
|
||||||
class AppState extends ChangeNotifier {
|
class AppState extends ChangeNotifier {
|
||||||
bool cwtchInit = false;
|
bool cwtchInit = false;
|
||||||
String appError = "";
|
String appError = "";
|
||||||
|
String? _selectedProfile;
|
||||||
|
String? _selectedConversation;
|
||||||
|
|
||||||
void SetCwtchInit() {
|
void SetCwtchInit() {
|
||||||
cwtchInit = true;
|
cwtchInit = true;
|
||||||
|
@ -74,6 +76,20 @@ class AppState extends ChangeNotifier {
|
||||||
appError = error;
|
appError = error;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? get selectedProfile => _selectedProfile;
|
||||||
|
set selectedProfile(String? newVal) {
|
||||||
|
this._selectedProfile = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get selectedConversation => _selectedConversation;
|
||||||
|
set selectedConversation(String? newVal) {
|
||||||
|
this._selectedConversation = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactListState extends ChangeNotifier {
|
class ContactListState extends ChangeNotifier {
|
||||||
|
@ -491,6 +507,8 @@ class MessageState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
set loaded(bool newVal) {
|
set loaded(bool newVal) {
|
||||||
|
// quickly-arriving messages get discarded before loading sometimes
|
||||||
|
if (!hasListeners) return;
|
||||||
this._loaded = newVal;
|
this._loaded = newVal;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,13 @@ import 'opaque.dart';
|
||||||
|
|
||||||
const TapirGroupsExperiment = "tapir-groups-experiment";
|
const TapirGroupsExperiment = "tapir-groups-experiment";
|
||||||
|
|
||||||
|
enum DualpaneMode {
|
||||||
|
Single,
|
||||||
|
Dual1to2,
|
||||||
|
Dual1to4,
|
||||||
|
CopyPortrait,
|
||||||
|
}
|
||||||
|
|
||||||
/// Settings govern the *Globally* relevant settings like Locale, Theme and Experiments.
|
/// Settings govern the *Globally* relevant settings like Locale, Theme and Experiments.
|
||||||
/// We also provide access to the version information here as it is also accessed from the
|
/// We also provide access to the version information here as it is also accessed from the
|
||||||
/// Settings Pane.
|
/// Settings Pane.
|
||||||
|
@ -19,8 +26,8 @@ class Settings extends ChangeNotifier {
|
||||||
// explicitly set experiments to false until told otherwise...
|
// explicitly set experiments to false until told otherwise...
|
||||||
bool experimentsEnabled = false;
|
bool experimentsEnabled = false;
|
||||||
HashMap<String, bool> experiments = HashMap.identity();
|
HashMap<String, bool> experiments = HashMap.identity();
|
||||||
String _uiColumnModePortrait = "Single";
|
DualpaneMode _uiColumnModePortrait = DualpaneMode.Single;
|
||||||
String _uiColumnModeLandscape = "Same";
|
DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait;
|
||||||
|
|
||||||
bool blockUnknownConnections = false;
|
bool blockUnknownConnections = false;
|
||||||
|
|
||||||
|
@ -75,6 +82,10 @@ class Settings extends ChangeNotifier {
|
||||||
// Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
|
// Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
|
||||||
experiments = new HashMap<String, bool>.from(settings["Experiments"]);
|
experiments = new HashMap<String, bool>.from(settings["Experiments"]);
|
||||||
|
|
||||||
|
// single pane vs dual pane preferences
|
||||||
|
_uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]);
|
||||||
|
_uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]);
|
||||||
|
|
||||||
// Push the experimental settings to Consumers of Settings
|
// Push the experimental settings to Consumers of Settings
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -136,18 +147,55 @@ class Settings extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get uiColumnModePortrait => _uiColumnModePortrait;
|
DualpaneMode get uiColumnModePortrait => _uiColumnModePortrait;
|
||||||
set uiColumnModePortrait(String newval) {
|
set uiColumnModePortrait(DualpaneMode newval) {
|
||||||
this._uiColumnModePortrait = newval;
|
this._uiColumnModePortrait = newval;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get uiColumnModeLandscape => _uiColumnModeLandscape;
|
DualpaneMode get uiColumnModeLandscape => _uiColumnModeLandscape;
|
||||||
set uiColumnModeLandscape(String newval) {
|
set uiColumnModeLandscape(DualpaneMode newval) {
|
||||||
this._uiColumnModeLandscape = newval;
|
this._uiColumnModeLandscape = newval;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<int> uiColumns(bool isLandscape) {
|
||||||
|
var m = (!isLandscape || uiColumnModeLandscape == DualpaneMode.CopyPortrait) ? uiColumnModePortrait : uiColumnModeLandscape;
|
||||||
|
switch(m) {
|
||||||
|
case DualpaneMode.Single: return [1];
|
||||||
|
case DualpaneMode.Dual1to2: return [1, 2];
|
||||||
|
case DualpaneMode.Dual1to4: return [1, 4];
|
||||||
|
}
|
||||||
|
print("impossible column configuration: portrait/$uiColumnModePortrait landscape/$uiColumnModeLandscape");
|
||||||
|
return [1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<DualpaneMode> uiColumnModeOptions(bool isLandscape) {
|
||||||
|
if (isLandscape) return [DualpaneMode.CopyPortrait, DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4,];
|
||||||
|
else return [DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4];
|
||||||
|
}
|
||||||
|
|
||||||
|
static DualpaneMode uiColumnModeFromString(String m) {
|
||||||
|
switch(m) {
|
||||||
|
case "DualpaneMode.Single": return DualpaneMode.Single;
|
||||||
|
case "DualpaneMode.Dual1to2": return DualpaneMode.Dual1to2;
|
||||||
|
case "DualpaneMode.Dual1to4": return DualpaneMode.Dual1to4;
|
||||||
|
case "DualpaneMode.CopyPortrait": return DualpaneMode.CopyPortrait;
|
||||||
|
}
|
||||||
|
print("Error: ui requested translation of column mode [$m] which doesn't exist");
|
||||||
|
return DualpaneMode.Single;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String uiColumnModeToString(DualpaneMode m) {
|
||||||
|
// todo: translate
|
||||||
|
switch(m) {
|
||||||
|
case DualpaneMode.Single: return "Single";
|
||||||
|
case DualpaneMode.Dual1to2: return "Double (1:2)";
|
||||||
|
case DualpaneMode.Dual1to4: return "Double (1:4)";
|
||||||
|
case DualpaneMode.CopyPortrait: return "Same as portrait mode setting";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a default settings object.
|
/// Construct a default settings object.
|
||||||
Settings(this.locale, this.theme);
|
Settings(this.locale, this.theme);
|
||||||
|
|
||||||
|
@ -164,7 +212,9 @@ class Settings extends ChangeNotifier {
|
||||||
"ExperimentsEnabled": this.experimentsEnabled,
|
"ExperimentsEnabled": this.experimentsEnabled,
|
||||||
"Experiments": experiments,
|
"Experiments": experiments,
|
||||||
"StateRootPane": 0,
|
"StateRootPane": 0,
|
||||||
"FirstTime": false
|
"FirstTime": false,
|
||||||
|
"UIColumnModePortrait": uiColumnModePortrait.toString(),
|
||||||
|
"UIColumnModeLandscape": uiColumnModeLandscape.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
|
import '../settings.dart';
|
||||||
import 'contactsview.dart';
|
import 'contactsview.dart';
|
||||||
import 'messageview.dart';
|
import 'messageview.dart';
|
||||||
|
|
||||||
|
@ -14,24 +15,25 @@ class DoubleColumnView extends StatefulWidget {
|
||||||
class _DoubleColumnViewState extends State<DoubleColumnView> {
|
class _DoubleColumnViewState extends State<DoubleColumnView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var flwtch = Provider.of<FlwtchState>(context);
|
var flwtch = Provider.of<AppState>(context);
|
||||||
|
var cols = Provider.of<Settings>(context).uiColumns(true);
|
||||||
return Flex(
|
return Flex(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[0],
|
flex: cols[0],
|
||||||
child: ContactsView(
|
child: ContactsView(
|
||||||
key: widget.key,
|
key: widget.key,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[1],
|
flex: cols[1],
|
||||||
child: flwtch.selectedConversation == ""
|
child: flwtch.selectedConversation == null
|
||||||
? Center(child: Text(AppLocalizations.of(context)!.addContactFirst))
|
? Card(child:Center(child: Text(AppLocalizations.of(context)!.addContactFirst)))
|
||||||
: //dev
|
: //dev
|
||||||
MultiProvider(providers: [
|
MultiProvider(providers: [
|
||||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
||||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation)!),
|
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)!),
|
||||||
], child: Container(child: MessageView())),
|
], child: Container(child: MessageView())),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -80,24 +80,33 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
secondary: Icon(CwtchIcons.change_theme, color: settings.current().mainTextColor()),
|
secondary: Icon(CwtchIcons.change_theme, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(/*AppLocalizations.of(context)!.settingLanguage*/ "UI Columns", style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(/*AppLocalizations.of(context)!.settingLanguage*/ "UI Columns in Portrait Mode", style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
||||||
trailing: DropdownButton(
|
trailing: DropdownButton(
|
||||||
value: "Single",
|
value: settings.uiColumnModePortrait.toString(),
|
||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
if (newValue == "Double (1:2)") {
|
settings.uiColumnModePortrait = Settings.uiColumnModeFromString(newValue!);
|
||||||
Provider.of<FlwtchState>(context).columns = [1, 2];
|
saveSettings(context);
|
||||||
} else if (newValue == "Double (1:4)") {
|
|
||||||
Provider.of<FlwtchState>(context).columns = [1, 4];
|
|
||||||
} else {
|
|
||||||
Provider.of<FlwtchState>(context).columns = [1];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// TODO: Only allow in landscape?
|
items: Settings.uiColumnModeOptions(false).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||||
items: (Platform.isAndroid ? ["Single"] : ["Single", "Double (1:2)", "Double (1:4)"]).map<DropdownMenuItem<String>>((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: value,
|
value: value.toString(),
|
||||||
child: Text(value),
|
child: Text(Settings.uiColumnModeToString(value)),
|
||||||
|
);
|
||||||
|
}).toList())),
|
||||||
|
ListTile(
|
||||||
|
title: Text(/*AppLocalizations.of(context)!.settingLanguage*/ "UI Columns in Landscape Mode", style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
|
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor()),
|
||||||
|
trailing: DropdownButton(
|
||||||
|
value: settings.uiColumnModeLandscape.toString(),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!);
|
||||||
|
saveSettings(context);
|
||||||
|
},
|
||||||
|
items: Settings.uiColumnModeOptions(true).map<DropdownMenuItem<String>>((DualpaneMode value) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: value.toString(),
|
||||||
|
child: Text(Settings.uiColumnModeToString(value)),
|
||||||
);
|
);
|
||||||
}).toList())),
|
}).toList())),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
|
|
|
@ -42,10 +42,13 @@ class _MessageViewState extends State<MessageView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var appState = Provider.of<AppState>(context);
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: _onWillPop,
|
onWillPop: _onWillPop,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
// setting leading to null makes it do the default behaviour; container() hides it
|
||||||
|
leading: Provider.of<Settings>(context).uiColumns(appState.isLandscape(context)).length > 1 ? Container() : null,
|
||||||
title: Text(Provider.of<ContactInfoState>(context).nickname),
|
title: Text(Provider.of<ContactInfoState>(context).nickname),
|
||||||
actions: [
|
actions: [
|
||||||
//IconButton(icon: Icon(Icons.chat), onPressed: _pushContactSettings),
|
//IconButton(icon: Icon(Icons.chat), onPressed: _pushContactSettings),
|
||||||
|
|
|
@ -42,41 +42,45 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
||||||
// Prevents Android back button from closing the app on the profile manager screen
|
// Prevents Android back button from closing the app on the profile manager screen
|
||||||
// (which would shutdown connections and all kinds of other expensive to generate things)
|
// (which would shutdown connections and all kinds of other expensive to generate things)
|
||||||
// TODO pop up a dialogue regarding closing the app?
|
// TODO pop up a dialogue regarding closing the app?
|
||||||
builder: (context, settings, child) => WillPopScope(
|
builder: (context, settings, child) =>
|
||||||
onWillPop: () async {
|
WillPopScope(
|
||||||
_showShutdown();
|
onWillPop: () async {
|
||||||
return closeApp;
|
_showShutdown();
|
||||||
},
|
return closeApp;
|
||||||
child: Scaffold(
|
},
|
||||||
backgroundColor: settings.theme.backgroundMainColor(),
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
backgroundColor: settings.theme.backgroundMainColor(),
|
||||||
title: Row(children: [
|
appBar: AppBar(
|
||||||
Image(
|
title: Row(children: [
|
||||||
image: AssetImage("assets/core/knott-white.png"),
|
Image(
|
||||||
filterQuality: FilterQuality.medium,
|
image: AssetImage("assets/core/knott-white.png"),
|
||||||
isAntiAlias: true,
|
filterQuality: FilterQuality.medium,
|
||||||
width: 32,
|
isAntiAlias: true,
|
||||||
height: 32,
|
width: 32,
|
||||||
colorBlendMode: BlendMode.dstIn,
|
height: 32,
|
||||||
color: Provider.of<Settings>(context).theme.backgroundHilightElementColor(),
|
colorBlendMode: BlendMode.dstIn,
|
||||||
|
color: Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.backgroundHilightElementColor(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
|
||||||
|
]),
|
||||||
|
actions: getActions(),
|
||||||
),
|
),
|
||||||
SizedBox(
|
floatingActionButton: FloatingActionButton(
|
||||||
width: 10,
|
onPressed: _pushAddEditProfile,
|
||||||
|
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
|
body: _buildProfileManager(),
|
||||||
]),
|
)),
|
||||||
actions: getActions(),
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: _pushAddEditProfile,
|
|
||||||
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
|
||||||
child: Icon(
|
|
||||||
Icons.add,
|
|
||||||
semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: _buildProfileManager(),
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,12 @@ import 'package:cwtch/views/profilemgrview.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
|
import '../model.dart';
|
||||||
|
import '../settings.dart';
|
||||||
import 'contactsview.dart';
|
import 'contactsview.dart';
|
||||||
import 'messageview.dart';
|
import 'messageview.dart';
|
||||||
|
|
||||||
|
// currently unused but maybe one day?
|
||||||
class TripleColumnView extends StatefulWidget {
|
class TripleColumnView extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_TripleColumnViewState createState() => _TripleColumnViewState();
|
_TripleColumnViewState createState() => _TripleColumnViewState();
|
||||||
|
@ -14,19 +17,22 @@ class TripleColumnView extends StatefulWidget {
|
||||||
class _TripleColumnViewState extends State<TripleColumnView> {
|
class _TripleColumnViewState extends State<TripleColumnView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var flwtch = Provider.of<FlwtchState>(context);
|
var appState = Provider.of<AppState>(context);
|
||||||
|
var settings = Provider.of<Settings>(context);
|
||||||
|
var columns = settings.uiColumns(appState.isLandscape(context));
|
||||||
|
|
||||||
return Flex(direction: Axis.horizontal, children: <Widget>[
|
return Flex(direction: Axis.horizontal, children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[0],
|
flex: columns[0],
|
||||||
child: ProfileMgrView(),
|
child: ProfileMgrView(),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[1],
|
flex: columns[1],
|
||||||
child: flwtch.selectedProfile == null ? Center(child: Text(AppLocalizations.of(context)!.createProfileToBegin)) : ContactsView(), //dev
|
child: appState.selectedProfile == null ? Center(child: Text(AppLocalizations.of(context)!.createProfileToBegin)) : ContactsView(), //dev
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: flwtch.columns[2],
|
flex: columns[2],
|
||||||
child: flwtch.selectedConversation == ""
|
child: appState.selectedConversation == null
|
||||||
? Center(child: Text(AppLocalizations.of(context)!.addContactFirst))
|
? Center(child: Text(AppLocalizations.of(context)!.addContactFirst))
|
||||||
: //dev
|
: //dev
|
||||||
Container(child: MessageView()),
|
Container(child: MessageView()),
|
||||||
|
|
|
@ -80,17 +80,19 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
]),
|
]),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
var flwtch = Provider.of<FlwtchState>(context, listen: false);
|
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||||
flwtch.setState(() => flwtch.selectedConversation = contact.onion);
|
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
|
||||||
// case 2/3 handled by Double/TripleColumnView respectively
|
// triggers update in Double/TripleColumnView
|
||||||
if (flwtch.columns.length == 1) _pushMessageView(contact.onion);
|
Provider.of<AppState>(context, listen: false).selectedConversation = contact.onion;
|
||||||
|
// if in singlepane mode, push to the stack
|
||||||
|
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||||
|
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushMessageView(String handle) {
|
void _pushMessageView(String handle) {
|
||||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
|
|
||||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
|
|
|
@ -30,29 +30,43 @@ class _ProfileRowState extends State<ProfileRow> {
|
||||||
padding: const EdgeInsets.all(2.0), //border size
|
padding: const EdgeInsets.all(2.0), //border size
|
||||||
child: ProfileImage(
|
child: ProfileImage(
|
||||||
badgeCount: 0,
|
badgeCount: 0,
|
||||||
badgeColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor(),
|
badgeColor: Provider
|
||||||
badgeTextColor: Provider.of<Settings>(context).theme.portraitProfileBadgeTextColor(),
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitProfileBadgeColor(),
|
||||||
|
badgeTextColor: Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitProfileBadgeTextColor(),
|
||||||
diameter: 64.0,
|
diameter: 64.0,
|
||||||
imagePath: profile.imagePath,
|
imagePath: profile.imagePath,
|
||||||
border: profile.isOnline ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor())),
|
border: profile.isOnline ? Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitOnlineBorderColor() : Provider
|
||||||
|
.of<Settings>(context)
|
||||||
|
.theme
|
||||||
|
.portraitOfflineBorderColor())),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
profile.nickname,
|
profile.nickname,
|
||||||
semanticsLabel: profile.nickname,
|
semanticsLabel: profile.nickname,
|
||||||
style: Provider.of<FlwtchState>(context).biggerFont,
|
style: Provider
|
||||||
softWrap: true,
|
.of<FlwtchState>(context)
|
||||||
overflow: TextOverflow.ellipsis,
|
.biggerFont,
|
||||||
),
|
softWrap: true,
|
||||||
ExcludeSemantics(
|
overflow: TextOverflow.ellipsis,
|
||||||
child: Text(
|
),
|
||||||
profile.onion,
|
ExcludeSemantics(
|
||||||
softWrap: true,
|
child: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
profile.onion,
|
||||||
))
|
softWrap: true,
|
||||||
],
|
overflow: TextOverflow.ellipsis,
|
||||||
)),
|
))
|
||||||
|
],
|
||||||
|
)),
|
||||||
IconButton(
|
IconButton(
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
||||||
|
@ -65,37 +79,35 @@ class _ProfileRowState extends State<ProfileRow> {
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
var flwtch = Provider.of<FlwtchState>(context, listen: false);
|
var appState = Provider.of<AppState>(context, listen: false);
|
||||||
flwtch.setState(() {
|
appState.selectedProfile = profile.onion;
|
||||||
flwtch.selectedProfile = profile;
|
appState.selectedConversation = null;
|
||||||
flwtch.selectedConversation = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (flwtch.columns.length) {
|
_pushContactList(profile, appState.isLandscape(context));//orientation == Orientation.landscape);
|
||||||
case 1:
|
|
||||||
_pushContactList(profile, false);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
_pushContactList(profile, true);
|
|
||||||
break;
|
|
||||||
} // case 3: handled by TripleColumnView
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushContactList(ProfileInfoState profile, bool includeDoublePane) {
|
void _pushContactList(ProfileInfoState profile, bool isLandscape) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
settings: RouteSettings(name: "conversations"),
|
settings: RouteSettings(name: "conversations"),
|
||||||
builder: (BuildContext buildcontext) {
|
builder: (BuildContext buildcontext) {
|
||||||
return MultiProvider(
|
return OrientationBuilder(
|
||||||
providers: [
|
builder: (orientationBuilderContext, orientation) {
|
||||||
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
return MultiProvider(
|
||||||
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList),
|
providers: [
|
||||||
],
|
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
||||||
builder: (context, widget) => includeDoublePane ? DoubleColumnView() : ContactsView(),
|
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList),
|
||||||
);
|
],
|
||||||
|
builder: (innercontext, widget) {
|
||||||
|
var appState = Provider.of<AppState>(context);
|
||||||
|
var settings = Provider.of<Settings>(context);
|
||||||
|
return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : ContactsView();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue