66#include "archive/archive_i.h"
77#include "assets_icons.h"
88
9+ #define SCROLL_INTERVAL (333)
10+ #define SCROLL_DELAY (2)
11+
12+ const char * units_short [] = {"B" , "K" , "M" , "G" , "T" };
13+
914typedef struct {
10- FuriString * name ;
15+ FuriString * path ;
1116 const Icon * icon ;
17+ char size_num [8 ];
18+ char size_unit [2 ];
1219} FileListItem ;
1320
1421typedef struct {
@@ -18,8 +25,35 @@ typedef struct {
1825 FileListItem * files ;
1926 size_t count ;
2027 size_t offset ;
28+ uint8_t scrollbar_y ;
29+ bool show_size ;
30+ uint8_t scrollbar_height ;
31+ size_t scroll_counter ;
32+ FuriTimer * scroll_timer ;
2133} FileListModel ;
2234
35+ static void format_file_size (
36+ uint64_t size ,
37+ char * num_buf ,
38+ size_t num_size ,
39+ char * unit_buf ,
40+ size_t unit_size ) {
41+ double formatted_size = size ;
42+ uint8_t unit = 0 ;
43+
44+ while (formatted_size >= 1024 && unit < COUNT_OF (units_short ) - 1 ) {
45+ formatted_size /= 1024 ;
46+ unit ++ ;
47+ }
48+
49+ if (unit == 0 ) {
50+ snprintf (num_buf , num_size , "%d" , (int )formatted_size );
51+ } else {
52+ snprintf (num_buf , num_size , "%.1f" , (double )formatted_size );
53+ }
54+ snprintf (unit_buf , unit_size , "%s" , units_short [unit ]);
55+ }
56+
2357static void widget_element_file_list_draw (Canvas * canvas , WidgetElement * element ) {
2458 furi_assert (canvas );
2559 furi_assert (element );
@@ -32,22 +66,53 @@ static void widget_element_file_list_draw(Canvas* canvas, WidgetElement* element
3266 if (idx < model -> count ) {
3367 canvas_draw_icon (
3468 canvas , model -> x + 2 , model -> y + (i * FRAME_HEIGHT ) - 9 , model -> files [idx ].icon );
35- canvas_draw_str (
69+
70+ size_t inner_x = 123 ;
71+ if (model -> show_size && model -> files [idx ].size_num [0 ] != '\0' ) {
72+ canvas_set_font (canvas , FontPrimary );
73+ uint16_t num_width = canvas_string_width (canvas , model -> files [idx ].size_num );
74+ canvas_set_font (canvas , FontSecondary );
75+ uint16_t unit_width = canvas_string_width (canvas , model -> files [idx ].size_unit );
76+ uint16_t total_width = num_width + unit_width ;
77+ inner_x = model -> x + (128 - model -> x ) - total_width - 5 ;
78+ inner_x -- ;
79+
80+ canvas_set_font (canvas , FontPrimary );
81+ canvas_draw_str (
82+ canvas , inner_x , model -> y + (i * FRAME_HEIGHT ), model -> files [idx ].size_num );
83+ canvas_set_font (canvas , FontSecondary );
84+ canvas_draw_str (
85+ canvas ,
86+ inner_x + num_width + 1 ,
87+ model -> y + (i * FRAME_HEIGHT ),
88+ model -> files [idx ].size_unit );
89+ }
90+
91+ size_t scroll_counter = model -> scroll_counter ;
92+ scroll_counter =
93+ i == 0 ? (model -> count > model -> lines &&
94+ (scroll_counter < SCROLL_DELAY ? 0 : scroll_counter - SCROLL_DELAY )) :
95+ 0 ;
96+
97+ elements_scrollable_text_line (
3698 canvas ,
3799 model -> x + 15 ,
38100 model -> y + (i * FRAME_HEIGHT ),
39- furi_string_get_cstr (model -> files [idx ].name ));
101+ inner_x - 19 ,
102+ model -> files [idx ].path ,
103+ scroll_counter ,
104+ i != 0 || model -> count <= model -> lines );
40105 }
41106 }
42107
43108 if (model -> count > model -> lines ) {
44109 elements_scrollbar_pos (
45110 canvas ,
46111 128 ,
47- model -> y - 9 ,
48- model -> lines * FRAME_HEIGHT ,
112+ model -> scrollbar_y ,
113+ model -> scrollbar_height ,
49114 model -> offset ,
50- model -> count - 2 );
115+ model -> count - ( model -> lines - 1 ) );
51116 }
52117}
53118
@@ -56,24 +121,37 @@ static bool widget_element_file_list_input(InputEvent* event, WidgetElement* ele
56121 FileListModel * model = element -> model ;
57122 bool consumed = false;
58123
59- if (event -> type == InputTypeShort || event -> type == InputTypeRepeat ) {
124+ if (model -> count > model -> lines &&
125+ (event -> type == InputTypeShort || event -> type == InputTypeRepeat )) {
60126 if (event -> key == InputKeyUp ) {
61- model -> offset = (model -> offset > 0 ) ? model -> offset - 1 : model -> count - 3 ;
127+ model -> offset = (model -> offset > 0 ) ? model -> offset - 1 : model -> count - model -> lines ;
128+ model -> scroll_counter = 0 ;
62129 consumed = true;
63130 } else if (event -> key == InputKeyDown ) {
64- model -> offset = ((model -> offset + 3 ) < model -> count ) ? model -> offset + 1 : 0 ;
131+ model -> offset = ((model -> offset + model -> lines ) < model -> count ) ? model -> offset + 1 :
132+ 0 ;
133+ model -> scroll_counter = 0 ;
65134 consumed = true;
66135 }
67136 }
68137
69138 return consumed ;
70139}
71140
141+ static void widget_element_file_list_timer_callback (void * context ) {
142+ WidgetElement * element = context ;
143+ FileListModel * file_model = element -> model ;
144+ file_model -> scroll_counter ++ ;
145+ with_view_model (element -> parent -> view , void * _model , { UNUSED (_model ); }, true);
146+ }
147+
72148static void widget_element_file_list_free (WidgetElement * element ) {
73149 furi_assert (element );
74150 FileListModel * model = element -> model ;
151+ furi_timer_stop (model -> scroll_timer );
152+ furi_timer_free (model -> scroll_timer );
75153 for (size_t i = 0 ; i < model -> count ; i ++ ) {
76- furi_string_free (model -> files [i ].name );
154+ furi_string_free (model -> files [i ].path );
77155 }
78156 free (model -> files );
79157 free (model );
@@ -86,26 +164,48 @@ WidgetElement* widget_element_file_list_create(
86164 uint8_t y ,
87165 uint8_t lines ,
88166 FuriString * * files ,
89- size_t count ) {
167+ size_t count ,
168+ uint8_t scrollbar_y ,
169+ uint8_t scrollbar_height ,
170+ bool show_size ) {
90171 // Allocate and init model
91172 FileListModel * model = malloc (sizeof (FileListModel ));
92173 model -> x = x ;
93174 model -> y = y ;
94175 model -> lines = lines ;
95176 model -> count = count ;
177+ model -> scrollbar_y = scrollbar_y ;
178+ model -> scrollbar_height = scrollbar_height ;
179+ model -> show_size = show_size ;
96180 model -> offset = 0 ;
181+ model -> scroll_counter = 0 ;
97182 model -> files = malloc (sizeof (FileListItem ) * count );
183+
98184 Storage * storage = furi_record_open (RECORD_STORAGE );
185+ FileInfo info ;
99186 for (size_t i = 0 ; i < count ; i ++ ) {
100- model -> files [i ].name = furi_string_alloc ();
101- path_extract_filename (files [i ], model -> files [i ].name , false);
187+ model -> files [i ].path = furi_string_alloc ();
188+ path_extract_filename (files [i ], model -> files [i ].path , false);
189+ model -> files [i ].size_num [0 ] = '\0' ;
190+ model -> files [i ].size_unit [0 ] = '\0' ;
191+
102192 if (storage_dir_exists (storage , furi_string_get_cstr (files [i ]))) {
103193 model -> files [i ].icon = & I_dir_10px ;
104194 } else {
105- const char * ext = strrchr (furi_string_get_cstr (model -> files [i ].name ), '.' );
195+ const char * ext = strrchr (furi_string_get_cstr (model -> files [i ].path ), '.' );
106196 model -> files [i ].icon = (ext && strcasecmp (ext , ".js" ) == 0 ) ? & I_js_script_10px :
107197 & I_unknown_10px ;
108198 }
199+
200+ if (show_size ) {
201+ storage_common_stat (storage , furi_string_get_cstr (files [i ]), & info );
202+ format_file_size (
203+ info .size ,
204+ model -> files [i ].size_num ,
205+ sizeof (model -> files [i ].size_num ),
206+ model -> files [i ].size_unit ,
207+ sizeof (model -> files [i ].size_unit ));
208+ }
109209 }
110210 furi_record_close (RECORD_STORAGE );
111211
@@ -116,5 +216,10 @@ WidgetElement* widget_element_file_list_create(
116216 element -> free = widget_element_file_list_free ;
117217 element -> parent = widget ;
118218 element -> model = model ;
219+
220+ model -> scroll_timer =
221+ furi_timer_alloc (widget_element_file_list_timer_callback , FuriTimerTypePeriodic , element );
222+ furi_timer_start (model -> scroll_timer , SCROLL_INTERVAL );
223+
119224 return element ;
120225}
0 commit comments