Fix release signing and unify "+" upload with the share-intent path
Build APK / build (push) Successful in 5m21s

- Release builds now sign with a dedicated keystore (local
  android/key.properties or CI secrets, both gitignored) instead of each
  machine's own debug key, so a downloaded release APK can actually
  update a previous install instead of failing with "App not installed"
  (mismatched signature).
- The Files tab's "+" -> "Upload File" now goes through the same
  ShareUploadView destination picker and background foreground-service
  upload as receiving a file via Android's "Share to..." sheet, instead
  of a separate blocking in-app-only upload path. Removed the
  now-unused ServerProvider/NextcloudService.uploadFileFromPath.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 17:17:46 -04:00
co-authored by Claude Sonnet 5
parent dd0c27035a
commit 722b66f7b1
8 changed files with 102 additions and 119 deletions
-18
View File
@@ -1574,24 +1574,6 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
_startCacheRefreshTimerIfNeeded();
}
Future<bool> uploadFileFromPath(
String name,
String localFilePath, {
void Function(int sent, int total)? onProgress,
}) async {
if (_service == null) return false;
final success = await _service!.uploadFileFromPath(
_currentFolderPath,
name,
localFilePath,
onProgress: onProgress,
);
if (success) {
await refreshData();
}
return success;
}
Future<bool> deleteItem(String itemPath) async {
if (_service == null) return false;
final success = await _service!.deleteItem(itemPath);
-38
View File
@@ -1103,44 +1103,6 @@ class NextcloudService {
return [];
}
/// Uploads a file from disk, streaming it so large files don't need to be
/// buffered fully in memory, with progress reporting.
Future<bool> uploadFileFromPath(
String folderPath,
String fileName,
String localFilePath, {
void Function(int sent, int total)? onProgress,
}) async {
var cleanPath = folderPath.trim();
if (!cleanPath.startsWith('/')) cleanPath = '/$cleanPath';
if (!cleanPath.endsWith('/')) cleanPath = '$cleanPath/';
final url =
'$_cleanServerUrl/remote.php/dav/files/$username$cleanPath$fileName';
debugPrint('[Nextcloud DAV] Streaming upload to $url');
final file = File(localFilePath);
final length = await file.length();
final dio = Dio();
final response = await dio.put<void>(
url,
data: file.openRead(),
options: Options(
headers: {..._headers, Headers.contentLengthHeader: length},
contentType: 'application/octet-stream',
),
onSendProgress: onProgress,
);
debugPrint(
'[Nextcloud DAV] Streaming upload status: ${response.statusCode}',
);
return response.statusCode == 201 ||
response.statusCode == 204 ||
response.statusCode == 200;
}
Future<bool> deleteItem(String itemPath) async {
var cleanPath = itemPath.trim();
if (!cleanPath.startsWith('/')) cleanPath = '/$cleanPath';
+23 -52
View File
@@ -9,6 +9,7 @@ import '../models/nextcloud_item.dart';
import '../models/selection_action.dart';
import '../providers/server_provider.dart';
import '../services/download_service.dart';
import '../services/share_intent_service.dart';
import '../widgets/breadcrumbs.dart';
import '../widgets/details/details_sheet.dart';
import '../widgets/item_icon.dart';
@@ -23,6 +24,7 @@ import '../widgets/swipeable_item.dart';
import '../widgets/synced_header_scaffold.dart';
import 'file_viewer_screen.dart';
import 'move_copy_destination_picker.dart';
import 'share_upload_view.dart';
class FilesView extends StatefulWidget {
final ScrollController scrollController;
@@ -1207,7 +1209,7 @@ class _FilesViewState extends State<FilesView>
title: const Text('Upload File'),
onTap: () {
Navigator.pop(sheetContext);
_pickAndUploadFile(context, provider);
_pickAndUploadFile(context);
},
),
ListTile(
@@ -1274,58 +1276,27 @@ class _FilesViewState extends State<FilesView>
);
}
Future<void> _pickAndUploadFile(
BuildContext context,
ServerProvider provider,
) async {
final result = await FilePicker.pickFiles();
if (result.isEmpty) return;
final picked = result.first;
if (picked.path == null) return;
final progress = ValueNotifier<double?>(0);
if (!context.mounted) return;
showDialog(
context: context,
barrierDismissible: false,
builder: (dialogContext) {
return AlertDialog(
title: Text('Uploading ${picked.name}'),
content: ValueListenableBuilder<double?>(
valueListenable: progress,
builder: (context, value, _) =>
LinearProgressIndicator(value: value),
// Mirrors `main.dart`'s `_handleSharedFiles` exactly, so picking a file
// via "+" lands on the same destination-picker screen and background
// foreground-service upload as receiving one via Android's "Share
// to..." sheet does, rather than a separate in-app-only upload path.
Future<void> _pickAndUploadFile(BuildContext context) async {
final picked = await FilePicker.pickFiles();
if (picked.isEmpty) return;
final files = picked
.where((f) => f.path != null)
.map(
(f) => SharedFileRef(
uri: Uri.file(f.path!).toString(),
name: f.name,
size: f.lengthSync(),
),
);
},
);
)
.toList();
if (files.isEmpty || !context.mounted) return;
bool success = false;
try {
success = await provider.uploadFileFromPath(
picked.name,
picked.path!,
onProgress: (sent, total) {
if (total > 0) progress.value = sent / total;
},
);
} catch (_) {
success = false;
}
if (context.mounted) {
Navigator.pop(context); // close progress dialog
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
success
? 'Uploaded ${picked.name} to Nextcloud'
: 'Failed to upload ${picked.name}',
),
behavior: SnackBarBehavior.floating,
),
);
}
Navigator.of(
context,
).push(MaterialPageRoute(builder: (_) => ShareUploadView(files: files)));
}
}