Unverified Commit c547e75b authored by RustDesk's avatar RustDesk Committed by GitHub

Merge pull request #1592 from Heap-Hop/mobile_feat_update_rebase

Update android 
parents 4377baf0 725c0689
......@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
......@@ -1035,3 +1035,29 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
}
return false;
}
/// Connect to a peer with [id].
/// If [isFileTransfer], starts a session only for file transfer.
/// If [isTcpTunneling], starts a session only for tcp tunneling.
/// If [isRDP], starts a session only for rdp.
void connect(BuildContext context, String id,
{bool isFileTransfer = false,
bool isTcpTunneling = false,
bool isRDP = false}) async {
if (id == '') return;
id = id.replaceAll(' ', '');
assert(!(isFileTransfer && isTcpTunneling && isRDP),
"more than one connect type");
FocusScopeNode currentFocus = FocusScope.of(context);
if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id);
} else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP);
} else {
await rustDeskWinManager.newRemoteDesktop(id);
}
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
}
This diff is collapsed.
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peer_widget.dart';
import 'package:flutter_hbb/common/widgets/peercard_widget.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
import '../../common.dart';
import '../../models/platform_model.dart';
class PeerTabPage extends StatefulWidget {
final List<String> tabs;
final List<Widget> children;
const PeerTabPage({required this.tabs, required this.children, Key? key})
: super(key: key);
@override
State<PeerTabPage> createState() => _PeerTabPageState();
}
class _PeerTabPageState extends State<PeerTabPage>
with SingleTickerProviderStateMixin {
late final PageController _pageController = PageController();
final RxInt _tabIndex = 0.obs;
@override
void initState() {
() async {
await bind.mainGetLocalOption(key: 'peer-tab-index').then((value) {
if (value == '') return;
final tab = int.parse(value);
_tabIndex.value = tab;
_pageController.jumpToPage(tab);
});
await bind.mainGetLocalOption(key: 'peer-card-ui-type').then((value) {
if (value == '') return;
final tab = int.parse(value);
peerCardUiType.value =
tab == PeerUiType.list.index ? PeerUiType.list : PeerUiType.grid;
});
}();
super.initState();
}
// hard code for now
Future<void> _handleTabSelection(int index) async {
// reset search text
peerSearchText.value = "";
peerSearchTextController.clear();
_tabIndex.value = index;
await bind.mainSetLocalOption(
key: 'peer-tab-index', value: index.toString());
_pageController.jumpToPage(index);
switch (index) {
case 0:
bind.mainLoadRecentPeers();
break;
case 1:
bind.mainLoadFavPeers();
break;
case 2:
bind.mainDiscover();
break;
case 3:
gFFI.abModel.getAb();
break;
}
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
textBaseline: TextBaseline.ideographic,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 28,
child: Container(
padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2),
constraints: isDesktop ? null : kMobilePageConstraints,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: _createTabBar(context)),
const SizedBox(width: 10),
const PeerSearchBar(),
Offstage(
offstage: !isDesktop,
child: _createPeerViewTypeSwitch(context)
.marginOnly(left: 13)),
],
)),
),
_createTabBarView(),
],
);
}
Widget _createTabBar(BuildContext context) {
return ListView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
controller: ScrollController(),
children: super.widget.tabs.asMap().entries.map((t) {
return Obx(() => InkWell(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: _tabIndex.value == t.key
? MyTheme.color(context).bg
: null,
borderRadius: BorderRadius.circular(2),
),
child: Align(
alignment: Alignment.center,
child: Text(
t.value,
textAlign: TextAlign.center,
style: TextStyle(
height: 1,
fontSize: 14,
color: _tabIndex.value == t.key
? MyTheme.color(context).text
: MyTheme.color(context).lightText),
),
)),
onTap: () async => await _handleTabSelection(t.key),
));
}).toList());
}
Widget _createTabBarView() {
final verticalMargin = isDesktop ? 12.0 : 6.0;
return Expanded(
child: PageView(
physics: isDesktop
? NeverScrollableScrollPhysics()
: BouncingScrollPhysics(),
controller: _pageController,
children: super.widget.children,
onPageChanged: (to) => _tabIndex.value = to)
.marginSymmetric(vertical: verticalMargin));
}
Widget _createPeerViewTypeSwitch(BuildContext context) {
final activeDeco = BoxDecoration(color: MyTheme.color(context).bg);
return Row(
children: [PeerUiType.grid, PeerUiType.list]
.map((type) => Obx(
() => Container(
padding: EdgeInsets.all(4.0),
decoration: peerCardUiType.value == type ? activeDeco : null,
child: InkWell(
onTap: () async {
await bind.mainSetLocalOption(
key: 'peer-card-ui-type',
value: type.index.toString());
peerCardUiType.value = type;
},
child: Icon(
type == PeerUiType.grid
? Icons.grid_view_rounded
: Icons.list,
size: 18,
color: peerCardUiType.value == type
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
)),
),
))
.toList(),
);
}
}
class PeerSearchBar extends StatefulWidget {
const PeerSearchBar({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _PeerSearchBarState();
}
class _PeerSearchBarState extends State<PeerSearchBar> {
var drawer = false;
@override
Widget build(BuildContext context) {
return drawer
? _buildSearchBar()
: IconButton(
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 2),
onPressed: () {
setState(() {
drawer = true;
});
},
icon: const Icon(
Icons.search_rounded,
color: MyTheme.dark,
));
}
Widget _buildSearchBar() {
RxBool focused = false.obs;
FocusNode focusNode = FocusNode();
focusNode.addListener(() => focused.value = focusNode.hasFocus);
return Container(
width: 120,
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
borderRadius: BorderRadius.circular(6),
),
child: Obx(() => Row(
children: [
Expanded(
child: Row(
children: [
Icon(
Icons.search_rounded,
color: MyTheme.color(context).placeholder,
).marginSymmetric(horizontal: 4),
Expanded(
child: TextField(
autofocus: true,
controller: peerSearchTextController,
onChanged: (searchText) {
peerSearchText.value = searchText;
},
focusNode: focusNode,
textAlign: TextAlign.start,
maxLines: 1,
cursorColor: MyTheme.color(context).lightText,
cursorHeight: 18,
cursorWidth: 1,
style: const TextStyle(fontSize: 14),
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 6),
hintText:
focused.value ? null : translate("Search ID"),
hintStyle: TextStyle(
fontSize: 14,
color: MyTheme.color(context).placeholder),
border: InputBorder.none,
isDense: true,
),
),
),
// Icon(Icons.close),
IconButton(
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 2),
onPressed: () {
setState(() {
peerSearchTextController.clear();
peerSearchText.value = "";
drawer = false;
});
},
icon: const Icon(
Icons.close,
color: MyTheme.dark,
)),
],
),
)
],
)),
);
}
}
......@@ -40,7 +40,7 @@ class _PeerWidget extends StatefulWidget {
/// State for the peer widget.
class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
static const int _maxQueryCount = 3;
final space = isDesktop ? 12.0 : 8.0;
final _curPeers = <String>{};
final _scrollController = ScrollController();
var _lastChangeTime = DateTime.now();
......@@ -49,6 +49,17 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
var _queryCoun = 0;
var _exit = false;
late final mobileWidth = () {
const minWidth = 320.0;
final windowWidth = MediaQuery.of(context).size.width;
var width = windowWidth - 2 * space;
if (windowWidth > minWidth + 2 * space) {
final n = (windowWidth / (minWidth + 2 * space)).floor();
width = windowWidth / n - 2 * space;
}
return width;
}();
_PeerWidgetState() {
_startCheckOnlines();
}
......@@ -78,72 +89,81 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
@override
Widget build(BuildContext context) {
const space = 12.0;
return ChangeNotifierProvider<Peers>(
create: (context) => widget.peers,
child: Consumer<Peers>(
builder: (context, peers, child) => peers.peers.isEmpty
? Center(
child: Text(translate("Empty")),
)
: DesktopScrollWrapper(
scrollController: _scrollController,
child: SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
controller: _scrollController,
child: ObxValue<RxString>((searchText) {
return FutureBuilder<List<Peer>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
final peers = snapshot.data!;
final cards = <Widget>[];
for (final peer in peers) {
cards.add(Offstage(
key: ValueKey("off${peer.id}"),
offstage: widget.offstageFunc(peer),
child: Obx(
() => SizedBox(
width: 220,
height:
peerCardUiType.value == PeerUiType.grid
? 140
: 42,
child: VisibilityDetector(
key: ValueKey(peer.id),
onVisibilityChanged: (info) {
final peerId =
(info.key as ValueKey).value;
if (info.visibleFraction > 0.00001) {
_curPeers.add(peerId);
} else {
_curPeers.remove(peerId);
}
_lastChangeTime = DateTime.now();
},
child: widget.peerCardWidgetFunc(peer),
),
),
)));
}
return Wrap(
spacing: space,
runSpacing: space,
children: cards);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
future: matchPeers(searchText.value, peers.peers),
);
}, peerSearchText),
),
),
),
builder: (context, peers, child) => peers.peers.isEmpty
? Center(
child: Text(translate("Empty")),
)
: _buildPeersView(peers)),
);
}
Widget _buildPeersView(Peers peers) {
final body = ObxValue<RxString>((searchText) {
return FutureBuilder<List<Peer>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
final peers = snapshot.data!;
final cards = <Widget>[];
for (final peer in peers) {
final visibilityChild = VisibilityDetector(
key: ValueKey(peer.id),
onVisibilityChanged: (info) {
final peerId = (info.key as ValueKey).value;
if (info.visibleFraction > 0.00001) {
_curPeers.add(peerId);
} else {
_curPeers.remove(peerId);
}
_lastChangeTime = DateTime.now();
},
child: widget.peerCardWidgetFunc(peer),
);
cards.add(Offstage(
key: ValueKey("off${peer.id}"),
offstage: widget.offstageFunc(peer),
child: isDesktop
? Obx(
() => SizedBox(
width: 220,
height: peerCardUiType.value == PeerUiType.grid
? 140
: 42,
child: visibilityChild,
),
)
: SizedBox(width: mobileWidth, child: visibilityChild)));
}
return Wrap(spacing: space, runSpacing: space, children: cards);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
future: matchPeers(searchText.value, peers.peers),
);
}, peerSearchText);
if (isDesktop) {
return DesktopScrollWrapper(
scrollController: _scrollController,
child: SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
controller: _scrollController,
child: body),
);
} else {
return SingleChildScrollView(
physics: BouncingScrollPhysics(),
controller: _scrollController,
child: body,
);
}
}
// ignore: todo
// TODO: variables walk through async tasks?
void _startCheckOnlines() {
......@@ -281,6 +301,7 @@ class AddressBookPeerWidget extends BasePeerWidget {
);
static List<Peer> _loadPeers() {
debugPrint("_loadPeers : ${gFFI.abModel.peers.toString()}");
return gFFI.abModel.peers.map((e) {
return Peer.fromJson(e['id'], e);
}).toList();
......@@ -304,7 +325,7 @@ class AddressBookPeerWidget extends BasePeerWidget {
@override
Widget build(BuildContext context) {
final widget = super.build(context);
gFFI.abModel.updateAb();
// gFFI.abModel.updateAb();
return widget;
}
}
......@@ -8,9 +8,8 @@ import '../../common/formatter/id_formatter.dart';
import '../../models/model.dart';
import '../../models/peer_model.dart';
import '../../models/platform_model.dart';
import './material_mod_popup_menu.dart' as mod_menu;
import './popup_menu.dart';
import './utils.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import '../../desktop/widgets/popup_menu.dart';
class _PopupMenuTheme {
static const Color commonColor = MyTheme.accent;
......@@ -55,6 +54,50 @@ class _PeerCardState extends State<_PeerCard>
@override
Widget build(BuildContext context) {
super.build(context);
if (isDesktop) {
return _buildDesktop();
} else {
return _buildMobile();
}
}
Widget _buildMobile() {
final peer = super.widget.peer;
return Card(
margin: EdgeInsets.symmetric(horizontal: 2),
child: GestureDetector(
onTap: !isWebDesktop ? () => connect(context, peer.id) : null,
onDoubleTap: isWebDesktop ? () => connect(context, peer.id) : null,
onLongPressStart: (details) {
final x = details.globalPosition.dx;
final y = details.globalPosition.dy;
_menuPos = RelativeRect.fromLTRB(x, y, x, y);
_showPeerMenu(peer.id);
},
child: ListTile(
contentPadding: const EdgeInsets.only(left: 12),
subtitle: Text('${peer.username}@${peer.hostname}'),
title: Text(formatID(peer.id)),
leading: Container(
padding: const EdgeInsets.all(6),
color: str2color('${peer.id}${peer.platform}', 0x7f),
child: getPlatformImage(peer.platform)),
trailing: InkWell(
child: const Padding(
padding: EdgeInsets.all(12),
child: Icon(Icons.more_vert)),
onTapDown: (e) {
final x = e.globalPosition.dx;
final y = e.globalPosition.dy;
_menuPos = RelativeRect.fromLTRB(x, y, x, y);
},
onTap: () {
_showPeerMenu(peer.id);
}),
)));
}
Widget _buildDesktop() {
final peer = super.widget.peer;
var deco = Rx<BoxDecoration?>(BoxDecoration(
border: Border.all(color: Colors.transparent, width: _borderWidth),
......@@ -264,7 +307,7 @@ class _PeerCardState extends State<_PeerCard>
final y = e.position.dy;
_menuPos = RelativeRect.fromLTRB(x, y, x, y);
},
onPointerUp: (_) => _showPeerMenu(context, peer.id),
onPointerUp: (_) => _showPeerMenu(peer.id),
child: MouseRegion(
onEnter: (_) => _iconMoreHover.value = true,
onExit: (_) => _iconMoreHover.value = false,
......@@ -281,7 +324,7 @@ class _PeerCardState extends State<_PeerCard>
/// Show the peer menu and handle user's choice.
/// User might remove the peer or send a file to the peer.
void _showPeerMenu(BuildContext context, String id) async {
void _showPeerMenu(String id) async {
await mod_menu.showMenu(
context: context,
position: _menuPos,
......
import 'package:flutter/material.dart';
const double kDesktopRemoteTabBarHeight = 28.0;
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)' , 'Desktop CM Page'
......@@ -24,6 +26,8 @@ const kWindowEdgeSize = 1.0;
const kInvalidValueStr = "InvalidValueStr";
const kMobilePageConstraints = BoxConstraints(maxWidth: 600);
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
/// see [LogicalKeyboardKey.keyLabel]
const Map<int, String> logicalKeyMap = <int, String>{
......
......@@ -15,7 +15,6 @@ import '../../models/platform_model.dart';
import '../../common/shared_state.dart';
import './popup_menu.dart';
import './material_mod_popup_menu.dart' as mod_menu;
import './utils.dart';
class _MenubarTheme {
static const Color commonColor = MyTheme.accent;
......
import 'package:flutter/material.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
/// Connect to a peer with [id].
/// If [isFileTransfer], starts a session only for file transfer.
/// If [isTcpTunneling], starts a session only for tcp tunneling.
/// If [isRDP], starts a session only for rdp.
void connect(BuildContext context, String id,
{bool isFileTransfer = false,
bool isTcpTunneling = false,
bool isRDP = false}) async {
if (id == '') return;
id = id.replaceAll(' ', '');
assert(!(isFileTransfer && isTcpTunneling && isRDP),
"more than one connect type");
FocusScopeNode currentFocus = FocusScope.of(context);
if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id);
} else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP);
} else {
await rustDeskWinManager.newRemoteDesktop(id);
}
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
}
......@@ -32,6 +32,7 @@ const url = 'https://rustdesk.com/';
final _hasIgnoreBattery = androidVersion >= 26;
var _ignoreBatteryOpt = false;
var _enableAbr = false;
var _isDarkMode = false;
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
String? username;
......@@ -59,6 +60,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_enableAbr = enableAbrRes;
}
_enableAbr = isDarkTheme();
if (update) {
setState(() {});
}
......@@ -173,7 +176,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
leading: Icon(Icons.translate),
onPressed: (context) {
showLanguageSettings(gFFI.dialogManager);
})
}),
SettingsTile.switchTile(
title: Text(translate('Dark Theme')),
leading: Icon(Icons.dark_mode),
initialValue: _isDarkMode,
onToggle: (v) {
setState(() {
_isDarkMode = !_isDarkMode;
MyTheme.changeTo(_isDarkMode);
});
},
)
]),
SettingsSection(
title: Text(translate("Enhancements")),
......
......@@ -1150,7 +1150,7 @@ packages:
name: wakelock
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.6"
version: "0.6.2"
wakelock_macos:
dependency: transitive
description:
......
......@@ -35,7 +35,7 @@ dependencies:
external_path: ^1.0.1
provider: ^6.0.3
tuple: ^2.0.0
wakelock: ^0.5.2
wakelock: ^0.6.2
device_info_plus: ^4.1.2
#firebase_analytics: ^9.1.5
package_info_plus: ^1.4.2
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment