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

Generated by: LCOV version 1.14