Merge branch 'main' into feature/webrtc
# Conflicts: # app/lib/pages/tabs/send_tab.dart
@@ -1,58 +1,93 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: Bug report
|
||||
labels: ["bug :bug:"]
|
||||
description: Create a report to help us address issues you are facing
|
||||
title: "bug: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: duplication
|
||||
attributes:
|
||||
label: ⠀
|
||||
options:
|
||||
- label: This issue is not duplicated with any other open or closed issues
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: latest
|
||||
attributes:
|
||||
label: ⠀
|
||||
options:
|
||||
- label: I am using the latest version from the release
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug_report_description
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is
|
||||
placeholder: |
|
||||
App crashes on startup every time after changing settings.
|
||||
/
|
||||
App crashes when sending/receiving massive files.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug_report_reproduce
|
||||
attributes:
|
||||
label: To reproduce
|
||||
description: Steps to reproduce the behavior
|
||||
value: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug_report_expected_behavior
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen
|
||||
placeholder: |
|
||||
App started normally, everything worked fine.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug_report_screenshots
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Steps to reproduce the bug
|
||||
placeholder: |
|
||||
1. Turn on "What" in "What settings"
|
||||
2. Restart the app
|
||||
3. Crash
|
||||
/
|
||||
1. Transfer a "What" file is What size takes What long
|
||||
2. Observe What
|
||||
3. Crash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Device information
|
||||
description: Provide details about your system environment
|
||||
placeholder: |
|
||||
Device: (Pixel 9 Pro/Lenovo Yoga 9i)
|
||||
System: (Android 16 BP31.2505/Windows 11 24H2)
|
||||
Special things: (Rooted/Hardware damaged)
|
||||
If applicable, both info of sender and receiver.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: |
|
||||
If applicable, add screenshots to help explain your problem
|
||||
Tip: You can attach images by clicking this area to highlight it and then dragging files in.
|
||||
- type: textarea
|
||||
id: bug_report_desktop
|
||||
attributes:
|
||||
label: Desktop (please complete the following information)
|
||||
description: If applicable, add screenshots to help explain your problem
|
||||
placeholder: |
|
||||
- OS: [e.g. iOS]
|
||||
- Version: [e.g. 1.6.2]
|
||||
modified_setting_items.jpg
|
||||
files_to_send.jpg
|
||||
crashed_screen.jpg
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: bug_report_smartphone
|
||||
attributes:
|
||||
label: Smartphone (please complete the following information)
|
||||
placeholder: |
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version: [e.g. 1.6.2]
|
||||
- type: textarea
|
||||
id: bug_report_additional_context
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here
|
||||
placeholder: |
|
||||
Crash report or other helpful information
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -41,9 +41,9 @@
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
android:name="io.flutter.embedding.android.EnableCutout"
|
||||
android:value="true"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
@@ -14,5 +15,6 @@
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
@@ -14,5 +15,6 @@
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -9,17 +9,25 @@ import Flutter
|
||||
) -> Bool {
|
||||
|
||||
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||
let channel = FlutterMethodChannel(name: "ios-delegate-channel",
|
||||
binaryMessenger: controller.engine.binaryMessenger)
|
||||
channel.setMethodCallHandler({
|
||||
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||
if call.method == "isReduceMotionEnabled" {
|
||||
result(UIAccessibility.isReduceMotionEnabled)
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
|
||||
if let engine = controller.engine {
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "ios-delegate-channel",
|
||||
binaryMessenger: engine.binaryMessenger
|
||||
)
|
||||
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
|
||||
if call.method == "isReduceMotionEnabled" {
|
||||
result(UIAccessibility.isReduceMotionEnabled)
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// I couldn't get the iOS build to run without this check
|
||||
print("Flutter engine is nil!")
|
||||
}
|
||||
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:common/api_route_builder.dart';
|
||||
import 'package:common/constants.dart';
|
||||
import 'package:common/isolate.dart';
|
||||
@@ -127,12 +128,14 @@ Future<RefenaContainer> preInit(List<String> args) async {
|
||||
} else if (defaultTargetPlatform == TargetPlatform.macOS) {
|
||||
startHidden = await isLaunchedAsLoginItem() && await getLaunchAtLoginMinimized();
|
||||
}
|
||||
|
||||
if (startHidden) {
|
||||
unawaited(hideToTray());
|
||||
} else {
|
||||
await WindowManager.instance.show();
|
||||
}
|
||||
|
||||
doWhenWindowReady(() {
|
||||
if (startHidden) {
|
||||
unawaited(hideToTray());
|
||||
} else {
|
||||
unawaited(showFromTray());
|
||||
}
|
||||
});
|
||||
|
||||
if (defaultTargetPlatform == TargetPlatform.macOS) {
|
||||
await setupStatusBar();
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/pages/debug/debug_page.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/local_send_logo.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:routerino/routerino.dart';
|
||||
@@ -23,9 +24,7 @@ class AboutPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.aboutPage.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.aboutPage.title),
|
||||
body: ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
children: [
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:localsend_app/gen/assets.gen.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/util/ui/nav_bar_padding.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
|
||||
class ChangelogPage extends StatelessWidget {
|
||||
const ChangelogPage();
|
||||
@@ -11,9 +12,7 @@ class ChangelogPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.changelogPage.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.changelogPage.title),
|
||||
body: FutureBuilder(
|
||||
future: rootBundle.loadString(Assets.changelog), // ignore: discarded_futures
|
||||
builder: (context, data) {
|
||||
@@ -22,8 +21,8 @@ class ChangelogPage extends StatelessWidget {
|
||||
}
|
||||
return Markdown(
|
||||
padding: EdgeInsets.only(
|
||||
left: 15,
|
||||
right: 15,
|
||||
left: 15 + MediaQuery.of(context).padding.left,
|
||||
right: 15 + MediaQuery.of(context).padding.right,
|
||||
top: 15,
|
||||
bottom: 15 + getNavBarPadding(context),
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:localsend_app/pages/debug/security_debug_page.dart';
|
||||
import 'package:localsend_app/provider/app_arguments_provider.dart';
|
||||
import 'package:localsend_app/provider/persistence_provider.dart';
|
||||
import 'package:localsend_app/util/shared_preferences/shared_preferences_file.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/debug_entry.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:routerino/routerino.dart';
|
||||
@@ -23,9 +24,7 @@ class DebugPage extends StatelessWidget {
|
||||
final store = SharedPreferencesStorePlatform.instance;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Debugging'),
|
||||
),
|
||||
appBar: basicLocalSendAppbar('Debugging'),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 30),
|
||||
children: [
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:localsend_app/provider/logging/discovery_logs_provider.dart';
|
||||
import 'package:localsend_app/provider/network/nearby_devices_provider.dart';
|
||||
import 'package:localsend_app/widget/copyable_text.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
@@ -16,9 +17,7 @@ class DiscoveryDebugPage extends StatelessWidget {
|
||||
final ref = context.ref;
|
||||
final logs = ref.watch(discoveryLoggerProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Discovery Debugging'),
|
||||
),
|
||||
appBar: basicLocalSendAppbar('Discovery Debugging'),
|
||||
body: ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
|
||||
children: [
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:localsend_app/provider/logging/http_logs_provider.dart';
|
||||
import 'package:localsend_app/widget/copyable_text.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
@@ -14,9 +15,7 @@ class HttpLogsPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final logs = context.ref.watch(httpLogsProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('HTTP Logs'),
|
||||
),
|
||||
appBar: basicLocalSendAppbar('HTTP Logs'),
|
||||
body: ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
|
||||
children: [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/provider/security_provider.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/debug_entry.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
@@ -11,9 +12,7 @@ class SecurityDebugPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final securityContext = context.ref.watch(securityProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Security Debugging'),
|
||||
),
|
||||
appBar: basicLocalSendAppbar('Security Debugging'),
|
||||
body: ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
|
||||
maxWidth: 700,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:localsend_app/model/state/purchase_state.dart';
|
||||
import 'package:localsend_app/pages/donation/donation_page_vm.dart';
|
||||
// [FOSS_REMOVE_START]
|
||||
import 'package:localsend_app/provider/purchase_provider.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
// [FOSS_REMOVE_END]
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
@@ -21,9 +22,7 @@ class DonationPage extends StatelessWidget {
|
||||
// [FOSS_REMOVE_END]
|
||||
builder: (context, vm) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.donationPage.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.donationPage.title),
|
||||
body: Stack(
|
||||
children: [
|
||||
ResponsiveListView(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/config/init.dart';
|
||||
@@ -11,6 +12,7 @@ import 'package:localsend_app/pages/tabs/send_tab.dart';
|
||||
import 'package:localsend_app/pages/tabs/settings_tab.dart';
|
||||
import 'package:localsend_app/provider/selection/selected_sending_files_provider.dart';
|
||||
import 'package:localsend_app/util/native/cross_file_converters.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
import 'package:localsend_app/widget/responsive_builder.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
@@ -100,62 +102,78 @@ class _HomePageState extends State<HomePage> with Refena {
|
||||
body: Row(
|
||||
children: [
|
||||
if (!sizingInformation.isMobile)
|
||||
NavigationRail(
|
||||
selectedIndex: vm.currentTab.index,
|
||||
onDestinationSelected: (index) => vm.changeTab(HomeTab.values[index]),
|
||||
extended: sizingInformation.isDesktop,
|
||||
backgroundColor: Theme.of(context).cardColorWithElevation,
|
||||
leading: sizingInformation.isDesktop
|
||||
? const Column(
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
'LocalSend',
|
||||
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
destinations: HomeTab.values.map((tab) {
|
||||
return NavigationRailDestination(
|
||||
icon: Icon(tab.icon),
|
||||
label: Text(tab.label),
|
||||
);
|
||||
}).toList(),
|
||||
Stack(
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: vm.currentTab.index,
|
||||
onDestinationSelected: (index) => vm.changeTab(HomeTab.values[index]),
|
||||
extended: sizingInformation.isDesktop,
|
||||
backgroundColor: Theme.of(context).cardColorWithElevation,
|
||||
leading: sizingInformation.isDesktop
|
||||
? Column(
|
||||
children: [
|
||||
checkPlatform([TargetPlatform.macOS])
|
||||
? // considered adding some extra space so it looks more natural
|
||||
SizedBox(height: 40)
|
||||
: SizedBox(height: 20),
|
||||
const Text(
|
||||
'LocalSend',
|
||||
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
)
|
||||
: checkPlatform([TargetPlatform.macOS])
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
)
|
||||
: null,
|
||||
destinations: HomeTab.values.map((tab) {
|
||||
return NavigationRailDestination(
|
||||
icon: Icon(tab.icon),
|
||||
label: Text(tab.label),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
// makes the top draggable
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 40,
|
||||
child: MoveWindow(),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: SafeArea(
|
||||
left: sizingInformation.isMobile,
|
||||
child: Stack(
|
||||
children: [
|
||||
PageView(
|
||||
controller: vm.controller,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
ReceiveTab(),
|
||||
SendTab(),
|
||||
SettingsTab(),
|
||||
],
|
||||
),
|
||||
if (_dragAndDropIndicator)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.file_download, size: 128),
|
||||
const SizedBox(height: 30),
|
||||
Text(t.sendTab.placeItems, style: Theme.of(context).textTheme.titleLarge),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
PageView(
|
||||
controller: vm.controller,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
SafeArea(child: ReceiveTab()),
|
||||
SafeArea(child: SendTab()),
|
||||
SettingsTab(),
|
||||
],
|
||||
),
|
||||
if (_dragAndDropIndicator)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.file_download, size: 128),
|
||||
const SizedBox(height: 30),
|
||||
Text(t.sendTab.placeItems, style: Theme.of(context).textTheme.titleLarge),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
@@ -27,9 +28,7 @@ class _LanguagePageState extends State<LanguagePage> {
|
||||
final t = Translations.of(context);
|
||||
final activeLocale = context.ref.watch(settingsProvider.select((s) => s.locale));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.settingsTab.general.language),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.sendTab.selection.title),
|
||||
body: ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
||||
children: [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:common/model/dto/file_dto.dart';
|
||||
import 'package:common/model/file_status.dart';
|
||||
import 'package:common/model/session_status.dart';
|
||||
@@ -20,6 +21,7 @@ import 'package:localsend_app/util/native/open_folder.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
import 'package:localsend_app/util/native/taskbar_helper.dart';
|
||||
import 'package:localsend_app/util/ui/nav_bar_padding.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/custom_progress_bar.dart';
|
||||
import 'package:localsend_app/widget/dialogs/cancel_session_dialog.dart';
|
||||
import 'package:localsend_app/widget/dialogs/error_dialog.dart';
|
||||
@@ -71,9 +73,19 @@ class _ProgressPageState extends State<ProgressPage> with Refena {
|
||||
|
||||
// Periodically call WakelockPlus.enable() to keep the screen awake
|
||||
_wakelockPlusTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
|
||||
try {
|
||||
unawaited(WakelockPlus.enable());
|
||||
} catch (_) {}
|
||||
final finished = ref.read(serverProvider)?.session?.files.values.map((e) => e.status).isFinishedOrSkipped ??
|
||||
ref.read(sendProvider)[widget.sessionId]?.files.values.map((e) => e.status).isFinishedOrSkipped ??
|
||||
true;
|
||||
if (finished) {
|
||||
timer.cancel();
|
||||
try {
|
||||
unawaited(WakelockPlus.disable());
|
||||
} catch (_) {}
|
||||
} else {
|
||||
try {
|
||||
unawaited(WakelockPlus.enable());
|
||||
} catch (_) {}
|
||||
}
|
||||
});
|
||||
|
||||
if (ref.read(settingsProvider).autoFinish) {
|
||||
@@ -222,11 +234,7 @@ class _ProgressPageState extends State<ProgressPage> with Refena {
|
||||
},
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
appBar: widget.showAppBar
|
||||
? AppBar(
|
||||
title: Text(title),
|
||||
)
|
||||
: null,
|
||||
appBar: widget.showAppBar ? basicLocalSendAppbar(title) : null,
|
||||
body: Stack(
|
||||
children: [
|
||||
ListView.builder(
|
||||
@@ -503,6 +511,15 @@ class _ProgressPageState extends State<ProgressPage> with Refena {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPlatform([TargetPlatform.macOS])
|
||||
? Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 40,
|
||||
child: MoveWindow(),
|
||||
)
|
||||
: SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:localsend_app/util/native/directories.dart';
|
||||
import 'package:localsend_app/util/native/open_file.dart';
|
||||
import 'package:localsend_app/util/native/open_folder.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/dialogs/file_info_dialog.dart';
|
||||
import 'package:localsend_app/widget/dialogs/history_clear_dialog.dart';
|
||||
import 'package:localsend_app/widget/file_thumbnail.dart';
|
||||
@@ -62,11 +63,8 @@ class ReceiveHistoryPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entries = context.watch(receiveHistoryProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.receiveHistoryPage.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.receiveHistoryPage.title),
|
||||
body: ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
children: [
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:localsend_app/provider/selection/selected_sending_files_provider
|
||||
import 'package:localsend_app/util/file_size_helper.dart';
|
||||
import 'package:localsend_app/util/native/open_file.dart';
|
||||
import 'package:localsend_app/util/ui/nav_bar_padding.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/dialogs/message_input_dialog.dart';
|
||||
import 'package:localsend_app/widget/file_thumbnail.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
@@ -22,9 +23,7 @@ class SelectedFilesPage extends StatelessWidget {
|
||||
final selectedFiles = ref.watch(selectedSendingFilesProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.sendTab.selection.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.sendTab.selection.title),
|
||||
body: ResponsiveListView.single(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
tabletPadding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:localsend_app/util/favorites.dart';
|
||||
import 'package:localsend_app/util/native/taskbar_helper.dart';
|
||||
import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
|
||||
import 'package:localsend_app/widget/animations/initial_slide_transition.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/dialogs/error_dialog.dart';
|
||||
import 'package:localsend_app/widget/list_tile/device_list_tile.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
@@ -86,7 +87,7 @@ class _SendPageState extends State<SendPage> with Refena {
|
||||
},
|
||||
canPop: true,
|
||||
child: Scaffold(
|
||||
appBar: widget.showAppBar ? AppBar() : null,
|
||||
appBar: widget.showAppBar ? basicLocalSendAppbar('') : null,
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:local_hero/local_hero.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/dialogs/text_field_tv.dart';
|
||||
import 'package:localsend_app/widget/labeled_checkbox.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
@@ -43,9 +44,7 @@ class _NetworkInterfacesPageState extends State<NetworkInterfacesPage> {
|
||||
? context.notifier(settingsProvider).setNetworkWhitelist
|
||||
: context.notifier(settingsProvider).setNetworkBlacklist;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.networkInterfacesPage.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.networkInterfacesPage.title),
|
||||
body: LocalHeroScope(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/pages/home_page.dart';
|
||||
@@ -6,6 +7,7 @@ import 'package:localsend_app/pages/receive_history_page.dart';
|
||||
import 'package:localsend_app/pages/tabs/receive_tab_vm.dart';
|
||||
import 'package:localsend_app/provider/animation_provider.dart';
|
||||
import 'package:localsend_app/util/ip_helper.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
|
||||
import 'package:localsend_app/widget/column_list_view.dart';
|
||||
import 'package:localsend_app/widget/custom_icon_button.dart';
|
||||
@@ -30,6 +32,9 @@ class ReceiveTab extends StatelessWidget {
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
checkPlatform([TargetPlatform.macOS])
|
||||
? SizedBox(height: 50, child: MoveWindow())
|
||||
: SizedBox(height: 0, width: 0), // makes the top part that's not occupied by another widget draggable
|
||||
Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: ResponsiveListView.defaultMaxWidth),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:common/model/device.dart';
|
||||
import 'package:common/model/session_status.dart';
|
||||
@@ -49,210 +50,221 @@ class SendTab extends StatelessWidget {
|
||||
final sizingInformation = SizingInformation(MediaQuery.sizeOf(context).width);
|
||||
final buttonWidth = sizingInformation.isDesktop ? BigButton.desktopWidth : BigButton.mobileWidth;
|
||||
final ref = context.ref;
|
||||
return ResponsiveListView(
|
||||
padding: EdgeInsets.zero,
|
||||
return Stack(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
if (vm.selectedFiles.isEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
|
||||
child: Text(
|
||||
t.sendTab.selection.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
HorizontalClipListView(
|
||||
outerHorizontalPadding: 15,
|
||||
outerVerticalPadding: 10,
|
||||
childPadding: 10,
|
||||
minChildWidth: buttonWidth,
|
||||
children: _options.map((option) {
|
||||
return BigButton(
|
||||
icon: option.icon,
|
||||
label: option.label,
|
||||
filled: false,
|
||||
onTap: () async => ref.global.dispatchAsync(PickFileAction(
|
||||
option: option,
|
||||
context: context,
|
||||
)),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
] else ...[
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15, top: 5, bottom: 15),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
t.sendTab.selection.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Spacer(),
|
||||
CustomIconButton(
|
||||
onPressed: () => ref.redux(selectedSendingFilesProvider).dispatch(ClearSelectionAction()),
|
||||
child: Icon(Icons.close, color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(t.sendTab.selection.files(files: vm.selectedFiles.length)),
|
||||
Text(t.sendTab.selection.size(size: vm.selectedFiles.fold(0, (prev, curr) => prev + curr.size).asReadableFileSize)),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
height: defaultThumbnailSize,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: vm.selectedFiles.length,
|
||||
itemBuilder: (context, index) {
|
||||
final file = vm.selectedFiles[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SmartFileThumbnail.fromCrossFile(file),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: () async {
|
||||
await context.push(() => const SelectedFilesPage());
|
||||
},
|
||||
child: Text(t.general.edit),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () async {
|
||||
if (_options.length == 1) {
|
||||
// open directly
|
||||
await ref.global.dispatchAsync(PickFileAction(
|
||||
option: _options.first,
|
||||
context: context,
|
||||
));
|
||||
return;
|
||||
}
|
||||
await AddFileDialog.open(
|
||||
context: context,
|
||||
options: _options,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(t.general.add),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Row(
|
||||
ResponsiveListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
const SizedBox(width: _horizontalPadding),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(t.sendTab.nearbyDevices, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 20),
|
||||
if (vm.selectedFiles.isEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
|
||||
child: Text(
|
||||
t.sendTab.selection.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
HorizontalClipListView(
|
||||
outerHorizontalPadding: 15,
|
||||
outerVerticalPadding: 10,
|
||||
childPadding: 10,
|
||||
minChildWidth: buttonWidth,
|
||||
children: _options.map((option) {
|
||||
return BigButton(
|
||||
icon: option.icon,
|
||||
label: option.label,
|
||||
filled: false,
|
||||
onTap: () async => ref.global.dispatchAsync(PickFileAction(
|
||||
option: option,
|
||||
context: context,
|
||||
)),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
] else ...[
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15, top: 5, bottom: 15),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
t.sendTab.selection.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Spacer(),
|
||||
CustomIconButton(
|
||||
onPressed: () => ref.redux(selectedSendingFilesProvider).dispatch(ClearSelectionAction()),
|
||||
child: Icon(Icons.close, color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(t.sendTab.selection.files(files: vm.selectedFiles.length)),
|
||||
Text(t.sendTab.selection.size(size: vm.selectedFiles.fold(0, (prev, curr) => prev + curr.size).asReadableFileSize)),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
height: defaultThumbnailSize,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: vm.selectedFiles.length,
|
||||
itemBuilder: (context, index) {
|
||||
final file = vm.selectedFiles[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SmartFileThumbnail.fromCrossFile(file),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: () async {
|
||||
await context.push(() => const SelectedFilesPage());
|
||||
},
|
||||
child: Text(t.general.edit),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () async {
|
||||
if (_options.length == 1) {
|
||||
// open directly
|
||||
await ref.global.dispatchAsync(PickFileAction(
|
||||
option: _options.first,
|
||||
context: context,
|
||||
));
|
||||
return;
|
||||
}
|
||||
await AddFileDialog.open(
|
||||
context: context,
|
||||
options: _options,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(t.general.add),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: _horizontalPadding),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(t.sendTab.nearbyDevices, style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_ScanButton(
|
||||
ips: vm.localIps,
|
||||
),
|
||||
Tooltip(
|
||||
message: t.sendTab.manualSending,
|
||||
child: CustomIconButton(
|
||||
onPressed: () async => vm.onTapAddress(context),
|
||||
child: const Icon(Icons.ads_click),
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: t.dialogs.favoriteDialog.title,
|
||||
child: CustomIconButton(
|
||||
onPressed: () async => await vm.onTapFavorite(context),
|
||||
child: const Icon(Icons.favorite),
|
||||
),
|
||||
),
|
||||
_SendModeButton(
|
||||
onSelect: (mode) async => vm.onTapSendMode(context, mode),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (vm.nearbyDevices.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
|
||||
child: Opacity(
|
||||
opacity: 0.3,
|
||||
child: DevicePlaceholderListTile(),
|
||||
),
|
||||
),
|
||||
...vm.nearbyDevices.map((device) {
|
||||
final favoriteEntry = vm.favoriteDevices.findDevice(device);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
|
||||
child: Hero(
|
||||
tag: 'device-${device.ip}',
|
||||
child: vm.sendMode == SendMode.multiple
|
||||
? _MultiSendDeviceListTile(
|
||||
device: device,
|
||||
isFavorite: favoriteEntry != null,
|
||||
nameOverride: favoriteEntry?.alias,
|
||||
vm: vm,
|
||||
)
|
||||
: DeviceListTile(
|
||||
device: device,
|
||||
isFavorite: favoriteEntry != null,
|
||||
nameOverride: favoriteEntry?.alias,
|
||||
onFavoriteTap: () async => await vm.onToggleFavorite(context, device),
|
||||
onTap: () async => await vm.onTapDevice(context, device),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
await context.push(() => const TroubleshootPage());
|
||||
},
|
||||
child: Text(t.troubleshootPage.title),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_ScanButton(
|
||||
ips: vm.localIps,
|
||||
),
|
||||
Tooltip(
|
||||
message: t.sendTab.manualSending,
|
||||
child: CustomIconButton(
|
||||
onPressed: () async => vm.onTapAddress(context),
|
||||
child: const Icon(Icons.ads_click),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
|
||||
child: Consumer(
|
||||
builder: (context, ref) {
|
||||
final animations = ref.watch(animationProvider);
|
||||
return OpacitySlideshow(
|
||||
durationMillis: 6000,
|
||||
running: animations,
|
||||
children: [
|
||||
Text(t.sendTab.help, style: const TextStyle(color: Colors.grey), textAlign: TextAlign.center),
|
||||
if (checkPlatformCanReceiveShareIntent())
|
||||
Text(t.sendTab.shareIntentInfo, style: const TextStyle(color: Colors.grey), textAlign: TextAlign.center),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: t.dialogs.favoriteDialog.title,
|
||||
child: CustomIconButton(
|
||||
onPressed: () async => await vm.onTapFavorite(context),
|
||||
child: const Icon(Icons.favorite),
|
||||
),
|
||||
),
|
||||
_SendModeButton(
|
||||
onSelect: (mode) async => vm.onTapSendMode(context, mode),
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
),
|
||||
if (vm.nearbyDevices.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
|
||||
child: Opacity(
|
||||
opacity: 0.3,
|
||||
child: DevicePlaceholderListTile(),
|
||||
),
|
||||
),
|
||||
...vm.nearbyDevices.map((device) {
|
||||
final favoriteEntry = vm.favoriteDevices.findDevice(device);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
|
||||
child: Hero(
|
||||
tag: 'device-${device.ip}',
|
||||
child: vm.sendMode == SendMode.multiple
|
||||
? _MultiSendDeviceListTile(
|
||||
device: device,
|
||||
isFavorite: favoriteEntry != null,
|
||||
nameOverride: favoriteEntry?.alias,
|
||||
vm: vm,
|
||||
)
|
||||
: DeviceListTile(
|
||||
device: device,
|
||||
isFavorite: favoriteEntry != null,
|
||||
nameOverride: favoriteEntry?.alias,
|
||||
onFavoriteTap: device.ip == null ? null : () async => await vm.onToggleFavorite(context, device),
|
||||
onTap: () async => await vm.onTapDevice(context, device),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
await context.push(() => const TroubleshootPage());
|
||||
},
|
||||
child: Text(t.troubleshootPage.title),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
|
||||
child: Consumer(
|
||||
builder: (context, ref) {
|
||||
final animations = ref.watch(animationProvider);
|
||||
return OpacitySlideshow(
|
||||
durationMillis: 6000,
|
||||
running: animations,
|
||||
children: [
|
||||
Text(t.sendTab.help, style: const TextStyle(color: Colors.grey), textAlign: TextAlign.center),
|
||||
if (checkPlatformCanReceiveShareIntent())
|
||||
Text(t.sendTab.shareIntentInfo, style: const TextStyle(color: Colors.grey), textAlign: TextAlign.center),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
// make the top draggable on Desktop
|
||||
checkPlatform([TargetPlatform.macOS])
|
||||
? SizedBox(height: 50, child: MoveWindow())
|
||||
: SizedBox(
|
||||
height: 0,
|
||||
width: 0,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/util/native/cmd_helper.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/custom_icon_button.dart';
|
||||
import 'package:localsend_app/widget/dialogs/not_available_on_platform_dialog.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
@@ -18,9 +19,7 @@ class TroubleshootPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final settings = context.ref.watch(settingsProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.troubleshootPage.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.troubleshootPage.title),
|
||||
body: ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 30),
|
||||
children: [
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:localsend_app/provider/network/server/server_provider.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
import 'package:localsend_app/util/ui/snackbar.dart';
|
||||
import 'package:localsend_app/widget/custom_basic_appbar.dart';
|
||||
import 'package:localsend_app/widget/dialogs/pin_dialog.dart';
|
||||
import 'package:localsend_app/widget/dialogs/qr_dialog.dart';
|
||||
import 'package:localsend_app/widget/dialogs/zoom_dialog.dart';
|
||||
@@ -99,9 +100,7 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
},
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.webSharePage.title),
|
||||
),
|
||||
appBar: basicLocalSendAppbar(t.webSharePage.title),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
if (_stateEnum != _ServerState.running) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'dart:convert' show utf8;
|
||||
import 'dart:convert' show jsonDecode, utf8;
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:localsend_app/util/send_ignore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:share_handler/share_handler.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
final _logger = Logger('SelectedSendingFiles');
|
||||
@@ -287,7 +288,27 @@ class LoadSelectionFromArgsAction extends AsyncReduxActionWithResult<SelectedSen
|
||||
@override
|
||||
Future<(List<CrossFile>, bool)> reduce() async {
|
||||
bool filesAdded = false;
|
||||
bool nextShare = false;
|
||||
for (final arg in args) {
|
||||
if (arg == '--share') {
|
||||
nextShare = true;
|
||||
continue;
|
||||
}
|
||||
if (nextShare) {
|
||||
nextShare = false;
|
||||
final json = jsonDecode(arg);
|
||||
final SharedMedia payload = SharedMedia.decode(json);
|
||||
final message = payload.content;
|
||||
if (message != null && message.trim().isNotEmpty) {
|
||||
dispatch(AddMessageAction(message: message));
|
||||
}
|
||||
await dispatchAsync(AddFilesAction(
|
||||
files: payload.attachments?.where((a) => a != null).cast<SharedAttachment>() ?? <SharedAttachment>[],
|
||||
converter: CrossFileConverters.convertSharedAttachment,
|
||||
));
|
||||
filesAdded = true;
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith('-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:common/model/file_type.dart';
|
||||
|
||||
/// Matches myFile-123 -> 123
|
||||
final _fileNumberRegex = RegExp(r'^(.*)(?:-(\d+))$');
|
||||
/// Matches myFile (123) -> "myFile", " (123)"
|
||||
final _fileNumberRegex = RegExp(r'^(.*)(?:(\s\(\d+\)))$');
|
||||
|
||||
extension FilePathStringExt on String {
|
||||
String get extension {
|
||||
@@ -43,9 +43,9 @@ extension FilePathStringExt on String {
|
||||
|
||||
final match = _fileNumberRegex.firstMatch(fileName);
|
||||
if (match != null) {
|
||||
return '${match.group(1)}-$count'.withExtension(extension);
|
||||
return '${match.group(1)} ($count)'.withExtension(extension);
|
||||
} else {
|
||||
return '$fileName-$count'.withExtension(extension);
|
||||
return '$fileName ($count)'.withExtension(extension);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:localsend_app/gen/assets.gen.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
@@ -81,6 +82,7 @@ Future<void> showFromTray() async {
|
||||
// This will crash on Windows
|
||||
// https://github.com/localsend/localsend/issues/32
|
||||
await windowManager.setSkipTaskbar(false);
|
||||
appWindow.show();
|
||||
}
|
||||
|
||||
// Enable animations
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
|
||||
class CustomBackButton extends StatelessWidget {
|
||||
final Color? color;
|
||||
|
||||
const CustomBackButton({super.key, this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isRtl = Directionality.of(context) == TextDirection.rtl;
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
isRtl ? Icons.arrow_forward_ios_rounded : Icons.arrow_back_ios_new_rounded,
|
||||
color: color ?? IconTheme.of(context).color,
|
||||
),
|
||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
onPressed: () async {
|
||||
await Navigator.maybePop(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PreferredSizeWidget basicLocalSendAppbar(String title) {
|
||||
// Creates a very simple new appBar to support bitsdojo_windows on mac and make them draggable
|
||||
// if you want have more items on here for a specific page, make sure to add it here as an option
|
||||
// so that mac users can still appreciate this near native new design
|
||||
return checkPlatform([TargetPlatform.macOS])
|
||||
? PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: ClipRRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 20.0,
|
||||
sigmaY: 20.0,
|
||||
),
|
||||
child: MoveWindow(
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Padding space for macOS traffic lights
|
||||
if (!kIsWeb && Platform.isMacOS) const SizedBox(width: 60),
|
||||
// Originally leading Icon
|
||||
CustomBackButton(),
|
||||
// Center Title
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: TextStyle(fontSize: 100, fontWeight: FontWeight.normal))),
|
||||
),
|
||||
)),
|
||||
// For true centering of the icon since it shifted
|
||||
const SizedBox(width: 60),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)))
|
||||
: AppBar(title: Text(title));
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
@@ -20,6 +21,9 @@
|
||||
#include <yaru_window_linux/yaru_window_linux_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
||||
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
|
||||
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_linux
|
||||
desktop_drop
|
||||
dynamic_color
|
||||
file_selector_linux
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import bitsdojo_window_macos
|
||||
import connectivity_plus
|
||||
import desktop_drop
|
||||
import device_info_plus
|
||||
@@ -28,6 +29,7 @@ import wakelock_plus
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
|
||||
@@ -3,6 +3,7 @@ import FlutterMacOS
|
||||
import Defaults
|
||||
import DockProgress
|
||||
import LaunchAtLogin
|
||||
import bitsdojo_window_macos
|
||||
|
||||
enum DockIcon: CaseIterable {
|
||||
case regular
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>AppIdentifierPrefix</key>
|
||||
<string>$(AppIdentifierPrefix)</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
import window_manager
|
||||
import bitsdojo_window_macos // used to make custom window bars on macOS (or any desktop operating system for that matter)
|
||||
|
||||
class MainFlutterWindow: NSWindow {
|
||||
class MainFlutterWindow: BitsdojoWindow {
|
||||
// just following intructions from https://pub.dev/packages/bitsdojo_window
|
||||
override func bitsdojo_window_configure() -> UInt {
|
||||
return BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP
|
||||
}
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController.init()
|
||||
let windowFrame = self.frame
|
||||
@@ -10,13 +15,9 @@ class MainFlutterWindow: NSWindow {
|
||||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
// window_manager: start window hidden
|
||||
hiddenWindowAtLaunch()
|
||||
|
||||
super.awakeFromNib()
|
||||
}
|
||||
|
||||
// window_manager: start hidden
|
||||
override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
|
||||
super.order(place, relativeTo: otherWin)
|
||||
hiddenWindowAtLaunch()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.0"
|
||||
bitsdojo_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bitsdojo_window
|
||||
sha256: "88ef7765dafe52d97d7a3684960fb5d003e3151e662c18645c1641c22b873195"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
bitsdojo_window_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_linux
|
||||
sha256: "9519c0614f98be733e0b1b7cb15b827007886f6fe36a4fb62cf3d35b9dd578ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
bitsdojo_window_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_macos
|
||||
sha256: f7c5be82e74568c68c5b8449e2c5d8fd12ec195ecd70745a7b9c0f802bb0268f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
bitsdojo_window_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_platform_interface
|
||||
sha256: "65daa015a0c6dba749bdd35a0f092e7a8ba8b0766aa0480eb3ef808086f6e27c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
bitsdojo_window_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bitsdojo_window_windows
|
||||
sha256: fa982cf61ede53f483e50b257344a1c250af231a3cdc93a7064dd6dc0d720b68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -11,6 +11,7 @@ environment:
|
||||
|
||||
dependencies:
|
||||
basic_utils: 5.7.0
|
||||
bitsdojo_window: ^0.1.6
|
||||
collection: ^1.17.2 # allow newer versions, so it can compile with newer Flutter versions
|
||||
common:
|
||||
path: ../common
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:localsend_app/util/file_path_helper.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('fileName with counter', () async {
|
||||
expect('myFile'.withCount(1), 'myFile (1)');
|
||||
expect('myFile (1)'.withCount(2), 'myFile (2)');
|
||||
});
|
||||
}
|
||||
@@ -15,3 +15,5 @@ x86/
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
localsend_msix_helper.msix
|
||||
|
||||
@@ -74,6 +74,13 @@ set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES
|
||||
"${BINARY_NAME}.exe.manifest"
|
||||
"localsend_msix_helper.msix"
|
||||
"install_msix_helper.ps1"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
@@ -22,6 +23,8 @@
|
||||
#include <windows_taskbar/windows_taskbar_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
BitsdojoWindowPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
DesktopDropPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_windows
|
||||
connectivity_plus
|
||||
desktop_drop
|
||||
dynamic_color
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Add-AppxPackage .\localsend_msix_helper.msix -ExternalLocation $(Get-Location)
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="0.0.0.0" name="localsendapp" />
|
||||
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
|
||||
publisher="CN=Tien Do Nam, O=Tien Do Nam, S=Sachsen, C=DE"
|
||||
packageName="com.flutter.localsendapp"
|
||||
applicationId="localsendapp"
|
||||
/>
|
||||
</assembly>
|
||||
@@ -11,6 +11,7 @@ add_executable(${BINARY_NAME} WIN32
|
||||
"main.cpp"
|
||||
"utils.cpp"
|
||||
"win32_window.cpp"
|
||||
"winrt_ext.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
"Runner.rc"
|
||||
"runner.exe.manifest"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
#include "winrt_ext.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
@@ -22,6 +23,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
std::vector<std::string> command_line_arguments =
|
||||
GetCommandLineArguments();
|
||||
|
||||
if (IsRunningWithIdentity()) {
|
||||
winrt::hstring share_arg = GetSharedMedia();
|
||||
if (!share_arg.empty()) {
|
||||
printf("share: %ls\n", share_arg.c_str());
|
||||
command_line_arguments.push_back("--share");
|
||||
command_line_arguments.push_back(Utf8FromUtf16(share_arg.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "winrt_ext.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <appmodel.h>
|
||||
#include <winrt/windows.foundation.h>
|
||||
#include <winrt/windows.foundation.collections.h>
|
||||
#include <winrt/windows.storage.h>
|
||||
#include <winrt/windows.storage.streams.h>
|
||||
#include <winrt/windows.applicationmodel.activation.h>
|
||||
#include <winrt/windows.applicationmodel.datatransfer.h>
|
||||
#include <winrt/windows.applicationmodel.datatransfer.sharetarget.h>
|
||||
#include <winrt/windows.data.json.h>
|
||||
|
||||
using winrt::Windows::ApplicationModel::AppInstance;
|
||||
using winrt::Windows::ApplicationModel::Activation::ActivationKind;
|
||||
using winrt::Windows::ApplicationModel::Activation::ShareTargetActivatedEventArgs;
|
||||
using winrt::Windows::ApplicationModel::DataTransfer::DataPackageView;
|
||||
using winrt::Windows::ApplicationModel::DataTransfer::StandardDataFormats;
|
||||
using winrt::Windows::Data::Json::JsonArray;
|
||||
using winrt::Windows::Data::Json::JsonObject;
|
||||
using winrt::Windows::Data::Json::JsonValue;
|
||||
|
||||
enum class SharedAttachmentType {
|
||||
IMAGE,
|
||||
VIDEO,
|
||||
AUDIO,
|
||||
FILE,
|
||||
};
|
||||
|
||||
bool IsRunningWithIdentity() {
|
||||
constexpr SIZE_T kPackageNameMaxLength = 1024;
|
||||
UINT32 length = kPackageNameMaxLength;
|
||||
wchar_t packageName[kPackageNameMaxLength];
|
||||
LONG result = GetCurrentPackageFullName(&length, packageName);
|
||||
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
winrt::hstring GetSharedMedia() {
|
||||
auto args = AppInstance::GetActivatedEventArgs();
|
||||
if (args == nullptr)
|
||||
return winrt::hstring();
|
||||
if (args.Kind() != ActivationKind::ShareTarget)
|
||||
return winrt::hstring();
|
||||
auto share_target_args = args.as<ShareTargetActivatedEventArgs>();
|
||||
auto op = share_target_args.ShareOperation();
|
||||
auto data = op.Data();
|
||||
JsonObject json;
|
||||
if (data.Contains(StandardDataFormats::Text())) {
|
||||
auto text = data.GetTextAsync().get();
|
||||
json.SetNamedValue(L"content", JsonValue::CreateStringValue(text));
|
||||
}
|
||||
if (data.Contains(StandardDataFormats::Uri())) {
|
||||
auto uri = data.GetUriAsync().get();
|
||||
json.SetNamedValue(L"content", JsonValue::CreateStringValue(uri.ToString()));
|
||||
}
|
||||
if (data.Contains(StandardDataFormats::StorageItems())) {
|
||||
JsonArray attachments;
|
||||
auto storage_items = data.GetStorageItemsAsync().get();
|
||||
for (const auto& item : storage_items) {
|
||||
JsonObject attachment;
|
||||
attachment.SetNamedValue(L"type", JsonValue::CreateNumberValue(double(SharedAttachmentType::FILE)));
|
||||
attachment.SetNamedValue(L"path", JsonValue::CreateStringValue(item.Path()));
|
||||
attachments.Append(attachment);
|
||||
}
|
||||
json.SetNamedValue(L"attachments", attachments);
|
||||
}
|
||||
return json.Stringify();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef RUNNER_WINRT_EXT_H_
|
||||
#define RUNNER_WINRT_EXT_H_
|
||||
|
||||
#include <winrt/base.h>
|
||||
|
||||
bool IsRunningWithIdentity();
|
||||
winrt::hstring GetSharedMedia();
|
||||
|
||||
#endif // RUNNER_WINRT_EXT_H_
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4"
|
||||
xmlns:uap6="http://schemas.microsoft.com/appx/manifest/uap/windows10/6"
|
||||
xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7"
|
||||
xmlns:uap8="http://schemas.microsoft.com/appx/manifest/uap/windows10/8"
|
||||
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||
xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
|
||||
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3"
|
||||
xmlns:rescap6="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/6"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:com2="http://schemas.microsoft.com/appx/manifest/com/windows10/2"
|
||||
xmlns:com3="http://schemas.microsoft.com/appx/manifest/com/windows10/3"
|
||||
IgnorableNamespaces="uap3 desktop">
|
||||
<Identity Name="11157TienDoNam.LocalSend" Version="1.14.0.0"
|
||||
Publisher="CN=Tien Do Nam, O=Tien Do Nam, S=Sachsen, C=DE" ProcessorArchitecture="x64" />
|
||||
<Properties>
|
||||
<DisplayName>LocalSend MSIX Helper</DisplayName>
|
||||
<PublisherDisplayName>Tien Do Nam</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
<Description>An open source cross-platform alternative to AirDrop</Description>
|
||||
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
|
||||
</Properties>
|
||||
<Resources>
|
||||
<Resource Language="en" /><Resource Language="ar" /><Resource Language="bn" /><Resource Language="cs" /><Resource Language="da" /><Resource Language="de" /><Resource Language="el" /><Resource Language="es-ES" /><Resource Language="eu" /><Resource Language="fa" /><Resource Language="fr" /><Resource Language="he" /><Resource Language="hu" /><Resource Language="in" /><Resource Language="it" /><Resource Language="ja" /><Resource Language="ko" /><Resource Language="ne" /><Resource Language="nl" /><Resource Language="pl" /><Resource Language="pt-BR" /><Resource Language="ru" /><Resource Language="sv" /><Resource Language="th" /><Resource Language="tr" /><Resource Language="uk" /><Resource Language="vi" /><Resource Language="zh-CN" /><Resource Language="zh-HK" /><Resource Language="zh-TW" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.2506" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
<Applications>
|
||||
<Application Id="localsendapp" Executable="localsend_app.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements AppListEntry="none" BackgroundColor="transparent"
|
||||
DisplayName="LocalSend" Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png" Description="An open source cross-platform alternative to AirDrop">
|
||||
<uap:DefaultTile ShortName="LocalSend" Square310x310Logo="Images\LargeTile.png"
|
||||
Square71x71Logo="Images\SmallTile.png" Wide310x150Logo="Images\Wide310x150Logo.png">
|
||||
<uap:ShowNameOnTiles>
|
||||
<uap:ShowOn Tile="square150x150Logo"/>
|
||||
<uap:ShowOn Tile="square310x310Logo"/>
|
||||
<uap:ShowOn Tile="wide310x150Logo"/>
|
||||
</uap:ShowNameOnTiles>
|
||||
</uap:DefaultTile>
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png"/>
|
||||
<uap:LockScreen BadgeLogo="Images\BadgeLogo.png" Notification="badge"/>
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.startupTask" Executable="localsend_app.exe" EntryPoint="Windows.FullTrustApplication" uap10:Parameters="autostart">
|
||||
<desktop:StartupTask TaskId="localsend" Enabled="false" DisplayName="LocalSend"/>
|
||||
</desktop:Extension>
|
||||
<uap:Extension Category="windows.shareTarget">
|
||||
<uap:ShareTarget Description="Send to Localsend">
|
||||
<uap:SupportedFileTypes>
|
||||
<uap:SupportsAnyFileType />
|
||||
</uap:SupportedFileTypes>
|
||||
<uap:DataFormat>Text</uap:DataFormat>
|
||||
<uap:DataFormat>Uri</uap:DataFormat>
|
||||
<uap:DataFormat>StorageItems</uap:DataFormat>
|
||||
</uap:ShareTarget>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
|
After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |