LCOV - code coverage report
Current view: top level - lib/views - filesharingview.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 67 113 59.3 %
Date: 2024-10-10 18:23:49 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:convert';
       2             : 
       3             : import 'package:cwtch/main.dart';
       4             : import 'package:cwtch/models/contact.dart';
       5             : import 'package:cwtch/models/profile.dart';
       6             : import 'package:cwtch/settings.dart';
       7             : import 'package:flutter/material.dart';
       8             : import 'package:provider/provider.dart';
       9             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
      10             : import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
      11             : 
      12             : import '../cwtch_icons_icons.dart';
      13             : 
      14             : class FileSharingView extends StatefulWidget {
      15           0 :   @override
      16           0 :   _FileSharingViewState createState() => _FileSharingViewState();
      17             : }
      18             : 
      19             : class _FileSharingViewState extends State<FileSharingView> {
      20           0 :   @override
      21             :   Widget build(BuildContext context) {
      22           0 :     var handle = Provider.of<ContactInfoState>(context).nickname;
      23           0 :     if (handle.isEmpty) {
      24           0 :       handle = Provider.of<ContactInfoState>(context).onion;
      25             :     }
      26             : 
      27           0 :     var profileHandle = Provider.of<ProfileInfoState>(context).onion;
      28             : 
      29           0 :     return Scaffold(
      30           0 :       appBar: AppBar(
      31           0 :         title: Text(handle + " ยป " + AppLocalizations.of(context)!.manageSharedFiles),
      32             :       ),
      33           0 :       body: FutureBuilder(
      34           0 :         future: Provider.of<FlwtchState>(context, listen: false).cwtch.GetSharedFiles(profileHandle, Provider.of<ContactInfoState>(context).identifier),
      35           0 :         builder: (context, snapshot) {
      36           0 :           if (snapshot.hasData) {
      37           0 :             List<dynamic> sharedFiles = jsonDecode(snapshot.data as String) ?? List<dynamic>.empty();
      38             :             // Stabilize sort when files are assigned identical DateShared.
      39           0 :             sharedFiles.sort((a, b) {
      40           0 :               final cmp = a["DateShared"].toString().compareTo(b["DateShared"].toString());
      41           0 :               return cmp == 0 ? a["FileKey"].compareTo(b["FileKey"]) : cmp;
      42             :             });
      43           0 :             sharedFiles = injectTimeHeaders(AppLocalizations.of(context), sharedFiles, DateTime.now());
      44             : 
      45           0 :             var fileList = ScrollablePositionedList.separated(
      46           0 :               itemScrollController: ItemScrollController(),
      47           0 :               itemCount: sharedFiles.length,
      48             :               shrinkWrap: true,
      49             :               reverse: true,
      50           0 :               physics: BouncingScrollPhysics(),
      51           0 :               semanticChildCount: sharedFiles.length,
      52           0 :               itemBuilder: (context, index) {
      53             :                 // New for #589: some entries in the time-sorted list 
      54             :                 // are placeholders for relative time headers. These should
      55             :                 // be placed in simple tiles for aesthetic pleasure.
      56             :                 // The dict only contains TimeHeader key, and as such
      57             :                 // the downstream logic needs to be avoided.
      58           0 :                 if (sharedFiles[index].containsKey("TimeHeader")) {
      59           0 :                   return ListTile(
      60           0 :                       title: Text(sharedFiles[index]["TimeHeader"]),
      61             :                       dense: true
      62             :                   );
      63             :                 }
      64           0 :                 String filekey = sharedFiles[index]["FileKey"];
      65             :                 // This makes the UI *very* slow when enabled. But can be useful for debugging
      66             :                 // Uncomment if necessary.
      67             :                 // EnvironmentConfig.debugLog("$sharedFiles " + sharedFiles[index].toString());
      68           0 :                 return SwitchListTile(
      69           0 :                     title: Text(sharedFiles[index]["Path"]),
      70           0 :                     subtitle: Text(sharedFiles[index]["DateShared"]),
      71           0 :                     value: sharedFiles[index]["Active"],
      72           0 :                     activeTrackColor: Provider.of<Settings>(context).theme.defaultButtonColor,
      73           0 :                     inactiveTrackColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
      74           0 :                     secondary: Icon(CwtchIcons.attached_file_3, color: Provider.of<Settings>(context).current().mainTextColor),
      75           0 :                     onChanged: (newValue) {
      76           0 :                       setState(() {
      77             :                         if (newValue) {
      78           0 :                           Provider.of<FlwtchState>(context, listen: false).cwtch.RestartSharing(profileHandle, filekey);
      79             :                         } else {
      80           0 :                           Provider.of<FlwtchState>(context, listen: false).cwtch.StopSharing(profileHandle, filekey);
      81             :                         }
      82             :                       });
      83             :                     });
      84             :               },
      85             :               // Here is seemingly approximately the location where date dividers could be injected.
      86             :               // See #589 fore description. Seems like this lambda could get refactored out into
      87             :               // a testable function that determines time in the past relative to now and emits
      88             :               // a localized text banner as necessary. Time and language localization creates
      89             :               // a possible difficulty for this one.
      90           0 :               separatorBuilder: (BuildContext context, int index) {
      91           0 :                 return Divider(height: 1);
      92             :               },
      93             :             );
      94             :             return fileList;
      95             :           }
      96           0 :           return Container();
      97             :         },
      98             :       ),
      99             :     );
     100             :   }
     101             : }
     102             : 
     103           1 : bool isToday(DateTime inputTimestamp, DateTime inputNow) {
     104           1 :   final localTimestamp = inputTimestamp.toLocal();
     105           1 :   final localNow = inputNow.toLocal();
     106           9 :   return localTimestamp.day == localNow.day && localTimestamp.month == localNow.month && localTimestamp.year == localNow.year;
     107             : }
     108             : 
     109           1 : bool isYesterday(DateTime inputTimestamp, DateTime inputNow) {
     110           1 :   final adjustedTimestamp = inputTimestamp.add(const Duration(hours: 24));
     111           1 :   return isToday(adjustedTimestamp, inputNow);
     112             : }
     113             : 
     114           1 : bool isThisWeek(DateTime inputTimestamp, DateTime inputNow) {
     115             :   // note that dart internally measures weeks starting from Mondays,
     116             :   // so the idea of something being earlier this week is finicky.
     117           1 :   final localTimestamp = inputTimestamp.toLocal();
     118           1 :   final localNow = inputNow.toLocal();
     119           8 :   final thisWeekLb = DateTime(localNow.year, localNow.month, localNow.day).subtract(Duration(days: localNow.weekday - 1, microseconds: 1));
     120           1 :   final thisWeekUb = thisWeekLb.add(const Duration(days: DateTime.daysPerWeek, microseconds: 1));
     121           2 :   return thisWeekLb.isBefore(localTimestamp) && thisWeekUb.isAfter(localTimestamp);
     122             : }
     123             : 
     124           1 : bool isLastWeek(DateTime inputTimestamp, DateTime inputNow) {
     125             :   // this inherits the same week definition issues as isThisWeek.
     126           3 :   final adjustedTimestamp = inputTimestamp.toLocal().add(Duration(days: DateTime.daysPerWeek));
     127           1 :   return isThisWeek(adjustedTimestamp, inputNow);
     128             : }
     129             : 
     130           1 : bool isThisMonth(DateTime inputTimestamp, DateTime inputNow) {
     131           1 :   final localTimestamp = inputTimestamp.toLocal();
     132           1 :   final localNow = inputNow.toLocal();
     133           6 :   return localTimestamp.month == localNow.month && localTimestamp.year == localNow.year;
     134             : }
     135             : 
     136           1 : bool isLastMonth(DateTime inputTimestamp, DateTime inputNow) {
     137           1 :   final localTimestamp = inputTimestamp.toLocal();
     138           1 :   final localNow = inputNow.toLocal();
     139          15 :   return (localTimestamp.year == localNow.year && localTimestamp.month == localNow.month - 1) || (localTimestamp.year == localNow.year - 1 && localTimestamp.month == DateTime.december && localNow.month == DateTime.january);
     140             : }
     141             : 
     142           1 : bool isThisYear(DateTime inputTimestamp, DateTime inputNow) {
     143           1 :   final localTimestamp = inputTimestamp.toLocal();
     144           1 :   final localNow = inputNow.toLocal();
     145           3 :   return localTimestamp.year == localNow.year;
     146             : }
     147             : 
     148           1 : bool isEarlierThanThisYear(DateTime inputTimestamp, DateTime inputNow) {
     149           1 :   final localTimestamp = inputTimestamp.toLocal();
     150           1 :   final localNow = inputNow.toLocal();
     151           3 :   return localTimestamp.year < localNow.year;
     152             : }
     153             : 
     154             : enum FileRelativeTime {
     155             :     Today,
     156             :     Yesterday,
     157             :     ThisWeek,
     158             :     LastWeek,
     159             :     ThisMonth,
     160             :     LastMonth,
     161             :     ThisYear,
     162             :     Earlier,
     163             : }
     164             : 
     165           1 : FileRelativeTime assignRelativeTime(DateTime timestamp, DateTime now) {
     166           1 :   if (isToday(timestamp, now)) {
     167             :     return FileRelativeTime.Today;
     168           1 :   } else if (isYesterday(timestamp, now)) {
     169             :     return FileRelativeTime.Yesterday;
     170           1 :   } else if (isThisWeek(timestamp, now)) {
     171             :     return FileRelativeTime.ThisWeek;
     172           1 :   } else if (isLastWeek(timestamp, now)) {
     173             :     return FileRelativeTime.LastWeek;
     174           1 :   } else if (isThisMonth(timestamp, now)) {
     175             :     return FileRelativeTime.ThisMonth;
     176           1 :   } else if (isLastMonth(timestamp, now)) {
     177             :     return FileRelativeTime.LastMonth;
     178           1 :   } else if (isThisYear(timestamp, now)) {
     179             :     return FileRelativeTime.ThisYear;
     180           1 :   } else if (isEarlierThanThisYear(timestamp, now)) {
     181             :     return FileRelativeTime.Earlier;
     182             :   } else {
     183           0 :     throw Exception("assignRelativeTime: impossible time comparison encountered, likely bug in cwtch-ui");
     184             :   }
     185             : }
     186             : 
     187           1 : String getLocalizedRelativeTime(dynamic localizer, FileRelativeTime t) {
     188             :   switch (t) {
     189           1 :     case FileRelativeTime.Today:
     190           1 :       return localizer!.relativeTimeToday;
     191           1 :     case FileRelativeTime.Yesterday:
     192           1 :       return localizer!.relativeTimeYesterday;
     193           1 :     case FileRelativeTime.ThisWeek:
     194           0 :       return localizer!.relativeTimeThisWeek;
     195           1 :     case FileRelativeTime.LastWeek:
     196           1 :       return localizer!.relativeTimeLastWeek;
     197           1 :     case FileRelativeTime.ThisMonth:
     198           0 :       return localizer!.relativeTimeThisMonth;
     199           1 :     case FileRelativeTime.LastMonth:
     200           1 :       return localizer!.relativeTimeLastMonth;
     201           1 :     case FileRelativeTime.ThisYear:
     202           1 :       return localizer!.relativeTimeThisYear;
     203           1 :     case FileRelativeTime.Earlier:
     204           1 :       return localizer!.relativeTimeEarlier;
     205             :     default:
     206             :       return "erroneous unlocalized time descriptor";
     207             :   }
     208             : }
     209             : 
     210           1 : List<dynamic> injectTimeHeaders(dynamic localizer, List<dynamic> sharedFiles, DateTime currentTime) {
     211           1 :   final result = <dynamic>[];
     212             :   var previousFileTime = FileRelativeTime.Today;
     213           3 :   for (var i = 0; i < sharedFiles.length; ++i) {
     214           3 :     final fileTime = DateTime.parse(sharedFiles[i]["DateShared"]);
     215           1 :     final relativeTime = assignRelativeTime(fileTime, currentTime);
     216           1 :     if (i == 0) {
     217             :       previousFileTime = relativeTime;
     218             :     }
     219           1 :     if (previousFileTime != relativeTime) {
     220           4 :       result.insert(result.length, {"TimeHeader": getLocalizedRelativeTime(localizer, previousFileTime)});
     221             :       previousFileTime = relativeTime;
     222             :     }
     223           3 :     result.insert(result.length, sharedFiles[i]);
     224           3 :     if (i == sharedFiles.length - 1) {
     225           4 :       result.insert(result.length, {"TimeHeader": getLocalizedRelativeTime(localizer, relativeTime)});
     226             :     }
     227             :   }
     228             :   return result;
     229             : }

Generated by: LCOV version 1.14