diff --git a/res/drawable-hdpi/ic_add_caption.png b/res/drawable-hdpi/ic_add_caption.png deleted file mode 100644 index 92cdffc7da..0000000000 Binary files a/res/drawable-hdpi/ic_add_caption.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_add_caption_36.png b/res/drawable-hdpi/ic_add_caption_36.png new file mode 100644 index 0000000000..3aa8cfb0f2 Binary files /dev/null and b/res/drawable-hdpi/ic_add_caption_36.png differ diff --git a/res/drawable-hdpi/ic_camera_filled_24.png b/res/drawable-hdpi/ic_camera_filled_24.png new file mode 100644 index 0000000000..9dd7a84582 Binary files /dev/null and b/res/drawable-hdpi/ic_camera_filled_24.png differ diff --git a/res/drawable-hdpi/ic_camera_front.png b/res/drawable-hdpi/ic_camera_front.png deleted file mode 100644 index 4895cfc794..0000000000 Binary files a/res/drawable-hdpi/ic_camera_front.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_camera_rear.png b/res/drawable-hdpi/ic_camera_rear.png deleted file mode 100644 index befb6e2fd7..0000000000 Binary files a/res/drawable-hdpi/ic_camera_rear.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_caption.png b/res/drawable-hdpi/ic_caption.png deleted file mode 100644 index d63dfa1d27..0000000000 Binary files a/res/drawable-hdpi/ic_caption.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_caption_28.png b/res/drawable-hdpi/ic_caption_28.png new file mode 100644 index 0000000000..04931524f8 Binary files /dev/null and b/res/drawable-hdpi/ic_caption_28.png differ diff --git a/res/drawable-hdpi/ic_check_circle_filled_36.png b/res/drawable-hdpi/ic_check_circle_filled_36.png new file mode 100644 index 0000000000..e00b6b5674 Binary files /dev/null and b/res/drawable-hdpi/ic_check_circle_filled_36.png differ diff --git a/res/drawable-hdpi/ic_create_album_filled_32.png b/res/drawable-hdpi/ic_create_album_filled_32.png new file mode 100644 index 0000000000..fc6159d9ce Binary files /dev/null and b/res/drawable-hdpi/ic_create_album_filled_32.png differ diff --git a/res/drawable-hdpi/ic_create_album_outline_32.png b/res/drawable-hdpi/ic_create_album_outline_32.png new file mode 100644 index 0000000000..7f5da89160 Binary files /dev/null and b/res/drawable-hdpi/ic_create_album_outline_32.png differ diff --git a/res/drawable-hdpi/ic_marker_36.png b/res/drawable-hdpi/ic_marker_36.png new file mode 100644 index 0000000000..dde1357bcb Binary files /dev/null and b/res/drawable-hdpi/ic_marker_36.png differ diff --git a/res/drawable-hdpi/ic_plus_28.png b/res/drawable-hdpi/ic_plus_28.png new file mode 100644 index 0000000000..b23db3489e Binary files /dev/null and b/res/drawable-hdpi/ic_plus_28.png differ diff --git a/res/drawable-hdpi/ic_scribble_brush.png b/res/drawable-hdpi/ic_scribble_brush.png deleted file mode 100644 index 6c31c106a3..0000000000 Binary files a/res/drawable-hdpi/ic_scribble_brush.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_scribble_delete.png b/res/drawable-hdpi/ic_scribble_delete.png deleted file mode 100644 index 582e992320..0000000000 Binary files a/res/drawable-hdpi/ic_scribble_delete.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_scribble_text.png b/res/drawable-hdpi/ic_scribble_text.png deleted file mode 100644 index 21e335c99c..0000000000 Binary files a/res/drawable-hdpi/ic_scribble_text.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_scribble_undo.png b/res/drawable-hdpi/ic_scribble_undo.png deleted file mode 100644 index 693cde60d2..0000000000 Binary files a/res/drawable-hdpi/ic_scribble_undo.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_switch_camera_36.png b/res/drawable-hdpi/ic_switch_camera_36.png new file mode 100644 index 0000000000..a09cb92cc2 Binary files /dev/null and b/res/drawable-hdpi/ic_switch_camera_36.png differ diff --git a/res/drawable-hdpi/ic_text_36.png b/res/drawable-hdpi/ic_text_36.png new file mode 100644 index 0000000000..5ad62dacec Binary files /dev/null and b/res/drawable-hdpi/ic_text_36.png differ diff --git a/res/drawable-hdpi/ic_trash_outline_36.png b/res/drawable-hdpi/ic_trash_outline_36.png new file mode 100644 index 0000000000..0cc7d2bd50 Binary files /dev/null and b/res/drawable-hdpi/ic_trash_outline_36.png differ diff --git a/res/drawable-hdpi/ic_undo_36.png b/res/drawable-hdpi/ic_undo_36.png new file mode 100644 index 0000000000..6b5ff3ee69 Binary files /dev/null and b/res/drawable-hdpi/ic_undo_36.png differ diff --git a/res/drawable-hdpi/ic_x_28.png b/res/drawable-hdpi/ic_x_28.png new file mode 100644 index 0000000000..e2f4911569 Binary files /dev/null and b/res/drawable-hdpi/ic_x_28.png differ diff --git a/res/drawable-mdpi/ic_add_caption.png b/res/drawable-mdpi/ic_add_caption.png deleted file mode 100644 index a80b6cf503..0000000000 Binary files a/res/drawable-mdpi/ic_add_caption.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_add_caption_36.png b/res/drawable-mdpi/ic_add_caption_36.png new file mode 100644 index 0000000000..f7f08d5de4 Binary files /dev/null and b/res/drawable-mdpi/ic_add_caption_36.png differ diff --git a/res/drawable-mdpi/ic_camera_filled_24.png b/res/drawable-mdpi/ic_camera_filled_24.png new file mode 100644 index 0000000000..fa5b99e7bf Binary files /dev/null and b/res/drawable-mdpi/ic_camera_filled_24.png differ diff --git a/res/drawable-mdpi/ic_camera_front.png b/res/drawable-mdpi/ic_camera_front.png deleted file mode 100644 index dcad59568c..0000000000 Binary files a/res/drawable-mdpi/ic_camera_front.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_camera_rear.png b/res/drawable-mdpi/ic_camera_rear.png deleted file mode 100644 index b124675c98..0000000000 Binary files a/res/drawable-mdpi/ic_camera_rear.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_caption.png b/res/drawable-mdpi/ic_caption.png deleted file mode 100644 index bc2a88f747..0000000000 Binary files a/res/drawable-mdpi/ic_caption.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_caption_28.png b/res/drawable-mdpi/ic_caption_28.png new file mode 100644 index 0000000000..bfb1305411 Binary files /dev/null and b/res/drawable-mdpi/ic_caption_28.png differ diff --git a/res/drawable-mdpi/ic_check_circle_filled_36.png b/res/drawable-mdpi/ic_check_circle_filled_36.png new file mode 100644 index 0000000000..0063fcdf6e Binary files /dev/null and b/res/drawable-mdpi/ic_check_circle_filled_36.png differ diff --git a/res/drawable-mdpi/ic_create_album_filled_32.png b/res/drawable-mdpi/ic_create_album_filled_32.png new file mode 100644 index 0000000000..db1838faca Binary files /dev/null and b/res/drawable-mdpi/ic_create_album_filled_32.png differ diff --git a/res/drawable-mdpi/ic_create_album_outline_32.png b/res/drawable-mdpi/ic_create_album_outline_32.png new file mode 100644 index 0000000000..52ac25a099 Binary files /dev/null and b/res/drawable-mdpi/ic_create_album_outline_32.png differ diff --git a/res/drawable-mdpi/ic_marker_36.png b/res/drawable-mdpi/ic_marker_36.png new file mode 100644 index 0000000000..89fe303da4 Binary files /dev/null and b/res/drawable-mdpi/ic_marker_36.png differ diff --git a/res/drawable-mdpi/ic_plus_28.png b/res/drawable-mdpi/ic_plus_28.png new file mode 100644 index 0000000000..20028b9af5 Binary files /dev/null and b/res/drawable-mdpi/ic_plus_28.png differ diff --git a/res/drawable-mdpi/ic_scribble_brush.png b/res/drawable-mdpi/ic_scribble_brush.png deleted file mode 100644 index fca4db97af..0000000000 Binary files a/res/drawable-mdpi/ic_scribble_brush.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_scribble_delete.png b/res/drawable-mdpi/ic_scribble_delete.png deleted file mode 100644 index 367b6bb696..0000000000 Binary files a/res/drawable-mdpi/ic_scribble_delete.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_scribble_text.png b/res/drawable-mdpi/ic_scribble_text.png deleted file mode 100644 index e116229292..0000000000 Binary files a/res/drawable-mdpi/ic_scribble_text.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_scribble_undo.png b/res/drawable-mdpi/ic_scribble_undo.png deleted file mode 100644 index 2cef26a92d..0000000000 Binary files a/res/drawable-mdpi/ic_scribble_undo.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_switch_camera_36.png b/res/drawable-mdpi/ic_switch_camera_36.png new file mode 100644 index 0000000000..f402ca9bdc Binary files /dev/null and b/res/drawable-mdpi/ic_switch_camera_36.png differ diff --git a/res/drawable-mdpi/ic_text_36.png b/res/drawable-mdpi/ic_text_36.png new file mode 100644 index 0000000000..5f04230da2 Binary files /dev/null and b/res/drawable-mdpi/ic_text_36.png differ diff --git a/res/drawable-mdpi/ic_trash_outline_36.png b/res/drawable-mdpi/ic_trash_outline_36.png new file mode 100644 index 0000000000..e14b9b2eea Binary files /dev/null and b/res/drawable-mdpi/ic_trash_outline_36.png differ diff --git a/res/drawable-mdpi/ic_undo_36.png b/res/drawable-mdpi/ic_undo_36.png new file mode 100644 index 0000000000..5e76ac0333 Binary files /dev/null and b/res/drawable-mdpi/ic_undo_36.png differ diff --git a/res/drawable-mdpi/ic_x_28.png b/res/drawable-mdpi/ic_x_28.png new file mode 100644 index 0000000000..b02b88e2ca Binary files /dev/null and b/res/drawable-mdpi/ic_x_28.png differ diff --git a/res/drawable-v21/media_camera_button_background.xml b/res/drawable-v21/media_camera_button_background.xml new file mode 100644 index 0000000000..21a75a84d1 --- /dev/null +++ b/res/drawable-v21/media_camera_button_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable-v21/mediarail_button_background.xml b/res/drawable-v21/mediarail_button_background.xml new file mode 100644 index 0000000000..035512fbff --- /dev/null +++ b/res/drawable-v21/mediarail_button_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/res/drawable-xhdpi/ic_add_caption.png b/res/drawable-xhdpi/ic_add_caption.png deleted file mode 100644 index 0bf6f3552f..0000000000 Binary files a/res/drawable-xhdpi/ic_add_caption.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_add_caption_36.png b/res/drawable-xhdpi/ic_add_caption_36.png new file mode 100644 index 0000000000..7d6a2c322f Binary files /dev/null and b/res/drawable-xhdpi/ic_add_caption_36.png differ diff --git a/res/drawable-xhdpi/ic_camera_filled_24.png b/res/drawable-xhdpi/ic_camera_filled_24.png new file mode 100644 index 0000000000..f91cf25d89 Binary files /dev/null and b/res/drawable-xhdpi/ic_camera_filled_24.png differ diff --git a/res/drawable-xhdpi/ic_camera_front.png b/res/drawable-xhdpi/ic_camera_front.png deleted file mode 100644 index f8143b9942..0000000000 Binary files a/res/drawable-xhdpi/ic_camera_front.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_camera_rear.png b/res/drawable-xhdpi/ic_camera_rear.png deleted file mode 100644 index 5e64fdbbee..0000000000 Binary files a/res/drawable-xhdpi/ic_camera_rear.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_caption.png b/res/drawable-xhdpi/ic_caption.png deleted file mode 100644 index 48d0d69c55..0000000000 Binary files a/res/drawable-xhdpi/ic_caption.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_caption_28.png b/res/drawable-xhdpi/ic_caption_28.png new file mode 100644 index 0000000000..eeba8d5ca4 Binary files /dev/null and b/res/drawable-xhdpi/ic_caption_28.png differ diff --git a/res/drawable-xhdpi/ic_check_circle_filled_36.png b/res/drawable-xhdpi/ic_check_circle_filled_36.png new file mode 100644 index 0000000000..625c7e9011 Binary files /dev/null and b/res/drawable-xhdpi/ic_check_circle_filled_36.png differ diff --git a/res/drawable-xhdpi/ic_create_album_filled_32.png b/res/drawable-xhdpi/ic_create_album_filled_32.png new file mode 100644 index 0000000000..142ba5ccff Binary files /dev/null and b/res/drawable-xhdpi/ic_create_album_filled_32.png differ diff --git a/res/drawable-xhdpi/ic_create_album_outline_32.png b/res/drawable-xhdpi/ic_create_album_outline_32.png new file mode 100644 index 0000000000..901e33cc20 Binary files /dev/null and b/res/drawable-xhdpi/ic_create_album_outline_32.png differ diff --git a/res/drawable-xhdpi/ic_marker_36.png b/res/drawable-xhdpi/ic_marker_36.png new file mode 100644 index 0000000000..cff3107234 Binary files /dev/null and b/res/drawable-xhdpi/ic_marker_36.png differ diff --git a/res/drawable-xhdpi/ic_plus_28.png b/res/drawable-xhdpi/ic_plus_28.png new file mode 100644 index 0000000000..362b614b5f Binary files /dev/null and b/res/drawable-xhdpi/ic_plus_28.png differ diff --git a/res/drawable-xhdpi/ic_scribble_brush.png b/res/drawable-xhdpi/ic_scribble_brush.png deleted file mode 100644 index 6dba282c76..0000000000 Binary files a/res/drawable-xhdpi/ic_scribble_brush.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_scribble_delete.png b/res/drawable-xhdpi/ic_scribble_delete.png deleted file mode 100644 index fc229e0724..0000000000 Binary files a/res/drawable-xhdpi/ic_scribble_delete.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_scribble_text.png b/res/drawable-xhdpi/ic_scribble_text.png deleted file mode 100644 index e30129ba63..0000000000 Binary files a/res/drawable-xhdpi/ic_scribble_text.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_scribble_undo.png b/res/drawable-xhdpi/ic_scribble_undo.png deleted file mode 100644 index b74a133d54..0000000000 Binary files a/res/drawable-xhdpi/ic_scribble_undo.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_switch_camera_36.png b/res/drawable-xhdpi/ic_switch_camera_36.png new file mode 100644 index 0000000000..9d80ebc202 Binary files /dev/null and b/res/drawable-xhdpi/ic_switch_camera_36.png differ diff --git a/res/drawable-xhdpi/ic_text_36.png b/res/drawable-xhdpi/ic_text_36.png new file mode 100644 index 0000000000..b9bbe7fdd0 Binary files /dev/null and b/res/drawable-xhdpi/ic_text_36.png differ diff --git a/res/drawable-xhdpi/ic_trash_outline_36.png b/res/drawable-xhdpi/ic_trash_outline_36.png new file mode 100644 index 0000000000..1721c329a4 Binary files /dev/null and b/res/drawable-xhdpi/ic_trash_outline_36.png differ diff --git a/res/drawable-xhdpi/ic_undo_36.png b/res/drawable-xhdpi/ic_undo_36.png new file mode 100644 index 0000000000..8d173d16ea Binary files /dev/null and b/res/drawable-xhdpi/ic_undo_36.png differ diff --git a/res/drawable-xhdpi/ic_x_28.png b/res/drawable-xhdpi/ic_x_28.png new file mode 100644 index 0000000000..539ee4ad7f Binary files /dev/null and b/res/drawable-xhdpi/ic_x_28.png differ diff --git a/res/drawable-xxhdpi/ic_add_caption.png b/res/drawable-xxhdpi/ic_add_caption.png deleted file mode 100644 index d423e74e3c..0000000000 Binary files a/res/drawable-xxhdpi/ic_add_caption.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_add_caption_36.png b/res/drawable-xxhdpi/ic_add_caption_36.png new file mode 100644 index 0000000000..1136f4a20a Binary files /dev/null and b/res/drawable-xxhdpi/ic_add_caption_36.png differ diff --git a/res/drawable-xxhdpi/ic_camera_filled_24.png b/res/drawable-xxhdpi/ic_camera_filled_24.png new file mode 100644 index 0000000000..2fbd146470 Binary files /dev/null and b/res/drawable-xxhdpi/ic_camera_filled_24.png differ diff --git a/res/drawable-xxhdpi/ic_camera_front.png b/res/drawable-xxhdpi/ic_camera_front.png deleted file mode 100644 index b053de97ed..0000000000 Binary files a/res/drawable-xxhdpi/ic_camera_front.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_camera_rear.png b/res/drawable-xxhdpi/ic_camera_rear.png deleted file mode 100644 index 788e621db7..0000000000 Binary files a/res/drawable-xxhdpi/ic_camera_rear.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_caption.png b/res/drawable-xxhdpi/ic_caption.png deleted file mode 100644 index 6892ef867c..0000000000 Binary files a/res/drawable-xxhdpi/ic_caption.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_caption_28.png b/res/drawable-xxhdpi/ic_caption_28.png new file mode 100644 index 0000000000..885fa26c3a Binary files /dev/null and b/res/drawable-xxhdpi/ic_caption_28.png differ diff --git a/res/drawable-xxhdpi/ic_check_circle_filled_36.png b/res/drawable-xxhdpi/ic_check_circle_filled_36.png new file mode 100644 index 0000000000..69c728f056 Binary files /dev/null and b/res/drawable-xxhdpi/ic_check_circle_filled_36.png differ diff --git a/res/drawable-xxhdpi/ic_create_album_filled_32.png b/res/drawable-xxhdpi/ic_create_album_filled_32.png new file mode 100644 index 0000000000..5aef02de30 Binary files /dev/null and b/res/drawable-xxhdpi/ic_create_album_filled_32.png differ diff --git a/res/drawable-xxhdpi/ic_create_album_outline_32.png b/res/drawable-xxhdpi/ic_create_album_outline_32.png new file mode 100644 index 0000000000..2e675e3b39 Binary files /dev/null and b/res/drawable-xxhdpi/ic_create_album_outline_32.png differ diff --git a/res/drawable-xxhdpi/ic_marker_36.png b/res/drawable-xxhdpi/ic_marker_36.png new file mode 100644 index 0000000000..388479091c Binary files /dev/null and b/res/drawable-xxhdpi/ic_marker_36.png differ diff --git a/res/drawable-xxhdpi/ic_plus_28.png b/res/drawable-xxhdpi/ic_plus_28.png new file mode 100644 index 0000000000..e09c7ae8c2 Binary files /dev/null and b/res/drawable-xxhdpi/ic_plus_28.png differ diff --git a/res/drawable-xxhdpi/ic_scribble_brush.png b/res/drawable-xxhdpi/ic_scribble_brush.png deleted file mode 100644 index 10c37572ce..0000000000 Binary files a/res/drawable-xxhdpi/ic_scribble_brush.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_scribble_delete.png b/res/drawable-xxhdpi/ic_scribble_delete.png deleted file mode 100644 index f127ff2c6d..0000000000 Binary files a/res/drawable-xxhdpi/ic_scribble_delete.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_scribble_text.png b/res/drawable-xxhdpi/ic_scribble_text.png deleted file mode 100644 index 7b091c9bac..0000000000 Binary files a/res/drawable-xxhdpi/ic_scribble_text.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_scribble_undo.png b/res/drawable-xxhdpi/ic_scribble_undo.png deleted file mode 100644 index 6ffae6f213..0000000000 Binary files a/res/drawable-xxhdpi/ic_scribble_undo.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_switch_camera_36.png b/res/drawable-xxhdpi/ic_switch_camera_36.png new file mode 100644 index 0000000000..b4b34852da Binary files /dev/null and b/res/drawable-xxhdpi/ic_switch_camera_36.png differ diff --git a/res/drawable-xxhdpi/ic_text_36.png b/res/drawable-xxhdpi/ic_text_36.png new file mode 100644 index 0000000000..22cbfb82ec Binary files /dev/null and b/res/drawable-xxhdpi/ic_text_36.png differ diff --git a/res/drawable-xxhdpi/ic_trash_outline_36.png b/res/drawable-xxhdpi/ic_trash_outline_36.png new file mode 100644 index 0000000000..2688b6cf5c Binary files /dev/null and b/res/drawable-xxhdpi/ic_trash_outline_36.png differ diff --git a/res/drawable-xxhdpi/ic_undo_36.png b/res/drawable-xxhdpi/ic_undo_36.png new file mode 100644 index 0000000000..b5877cfcb5 Binary files /dev/null and b/res/drawable-xxhdpi/ic_undo_36.png differ diff --git a/res/drawable-xxhdpi/ic_x_28.png b/res/drawable-xxhdpi/ic_x_28.png new file mode 100644 index 0000000000..14fc11201a Binary files /dev/null and b/res/drawable-xxhdpi/ic_x_28.png differ diff --git a/res/drawable-xxxhdpi/ic_add_caption.png b/res/drawable-xxxhdpi/ic_add_caption.png deleted file mode 100644 index cb17f42275..0000000000 Binary files a/res/drawable-xxxhdpi/ic_add_caption.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_add_caption_36.png b/res/drawable-xxxhdpi/ic_add_caption_36.png new file mode 100644 index 0000000000..89052339c7 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_add_caption_36.png differ diff --git a/res/drawable-xxxhdpi/ic_camera_filled_24.png b/res/drawable-xxxhdpi/ic_camera_filled_24.png new file mode 100644 index 0000000000..0f54e607a3 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_camera_filled_24.png differ diff --git a/res/drawable-xxxhdpi/ic_camera_front.png b/res/drawable-xxxhdpi/ic_camera_front.png deleted file mode 100644 index 69726bd2fe..0000000000 Binary files a/res/drawable-xxxhdpi/ic_camera_front.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_camera_rear.png b/res/drawable-xxxhdpi/ic_camera_rear.png deleted file mode 100644 index 7ec77723b3..0000000000 Binary files a/res/drawable-xxxhdpi/ic_camera_rear.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_caption.png b/res/drawable-xxxhdpi/ic_caption.png deleted file mode 100644 index 5ce643d2d1..0000000000 Binary files a/res/drawable-xxxhdpi/ic_caption.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_caption_28.png b/res/drawable-xxxhdpi/ic_caption_28.png new file mode 100644 index 0000000000..3d4b47158b Binary files /dev/null and b/res/drawable-xxxhdpi/ic_caption_28.png differ diff --git a/res/drawable-xxxhdpi/ic_check_circle_filled_36.png b/res/drawable-xxxhdpi/ic_check_circle_filled_36.png new file mode 100644 index 0000000000..2edd336b38 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_check_circle_filled_36.png differ diff --git a/res/drawable-xxxhdpi/ic_create_album_filled_32.png b/res/drawable-xxxhdpi/ic_create_album_filled_32.png new file mode 100644 index 0000000000..7431841200 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_create_album_filled_32.png differ diff --git a/res/drawable-xxxhdpi/ic_create_album_outline_32.png b/res/drawable-xxxhdpi/ic_create_album_outline_32.png new file mode 100644 index 0000000000..63ccb10945 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_create_album_outline_32.png differ diff --git a/res/drawable-xxxhdpi/ic_marker_36.png b/res/drawable-xxxhdpi/ic_marker_36.png new file mode 100644 index 0000000000..529b07b71b Binary files /dev/null and b/res/drawable-xxxhdpi/ic_marker_36.png differ diff --git a/res/drawable-xxxhdpi/ic_scribble_brush.png b/res/drawable-xxxhdpi/ic_scribble_brush.png deleted file mode 100644 index 38e12c3284..0000000000 Binary files a/res/drawable-xxxhdpi/ic_scribble_brush.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_scribble_delete.png b/res/drawable-xxxhdpi/ic_scribble_delete.png deleted file mode 100644 index 53dcb4fdaf..0000000000 Binary files a/res/drawable-xxxhdpi/ic_scribble_delete.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_scribble_text.png b/res/drawable-xxxhdpi/ic_scribble_text.png deleted file mode 100644 index 001428bfa7..0000000000 Binary files a/res/drawable-xxxhdpi/ic_scribble_text.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_scribble_undo.png b/res/drawable-xxxhdpi/ic_scribble_undo.png deleted file mode 100644 index b3b526b2ec..0000000000 Binary files a/res/drawable-xxxhdpi/ic_scribble_undo.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_switch_camera_36.png b/res/drawable-xxxhdpi/ic_switch_camera_36.png new file mode 100644 index 0000000000..30fec8d532 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_switch_camera_36.png differ diff --git a/res/drawable-xxxhdpi/ic_text_36.png b/res/drawable-xxxhdpi/ic_text_36.png new file mode 100644 index 0000000000..b1f4eed532 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_text_36.png differ diff --git a/res/drawable-xxxhdpi/ic_trash_outline_36.png b/res/drawable-xxxhdpi/ic_trash_outline_36.png new file mode 100644 index 0000000000..2fccd17e7a Binary files /dev/null and b/res/drawable-xxxhdpi/ic_trash_outline_36.png differ diff --git a/res/drawable-xxxhdpi/ic_undo_36.png b/res/drawable-xxxhdpi/ic_undo_36.png new file mode 100644 index 0000000000..44300d0f54 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_undo_36.png differ diff --git a/res/drawable-xxxhdpi/ic_x_28.png b/res/drawable-xxxhdpi/ic_x_28.png new file mode 100644 index 0000000000..907056a5af Binary files /dev/null and b/res/drawable-xxxhdpi/ic_x_28.png differ diff --git a/res/drawable/compose_background_camera.xml b/res/drawable/compose_background_camera.xml index cfb74f334c..e9b6aa6954 100644 --- a/res/drawable/compose_background_camera.xml +++ b/res/drawable/compose_background_camera.xml @@ -5,10 +5,7 @@ - - + android:color="@color/core_white" /> diff --git a/res/drawable/media_camera_button_background.xml b/res/drawable/media_camera_button_background.xml new file mode 100644 index 0000000000..d928269be8 --- /dev/null +++ b/res/drawable/media_camera_button_background.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/drawable/mediapicker_item_border_dark.xml b/res/drawable/mediapicker_item_border_dark.xml index d77188b203..4b5741d8ec 100644 --- a/res/drawable/mediapicker_item_border_dark.xml +++ b/res/drawable/mediapicker_item_border_dark.xml @@ -3,6 +3,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + + diff --git a/res/drawable/media_rail_item_background.xml b/res/drawable/mediarail_button_background.xml similarity index 60% rename from res/drawable/media_rail_item_background.xml rename to res/drawable/mediarail_button_background.xml index f9cceab4f7..09e616ea41 100644 --- a/res/drawable/media_rail_item_background.xml +++ b/res/drawable/mediarail_button_background.xml @@ -1,4 +1,5 @@ - + + \ No newline at end of file diff --git a/res/drawable/mediarail_media_outline.xml b/res/drawable/mediarail_media_outline.xml new file mode 100644 index 0000000000..8b6e4ec97f --- /dev/null +++ b/res/drawable/mediarail_media_outline.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/camera_controls_landscape.xml b/res/layout/camera_controls_landscape.xml index d25e4ed499..4ed346376c 100644 --- a/res/layout/camera_controls_landscape.xml +++ b/res/layout/camera_controls_landscape.xml @@ -23,7 +23,7 @@ android:layout_above="@+id/camera_capture_button" android:layout_marginBottom="40dp" android:layout_centerHorizontal="true" - android:src="@drawable/ic_camera_front" + android:src="@drawable/ic_switch_camera_36" android:scaleType="fitCenter" android:background="?selectableItemBackgroundBorderless" android:visibility="gone" diff --git a/res/layout/camera_controls_portrait.xml b/res/layout/camera_controls_portrait.xml index 33fe8eda32..c1dc8cfdf0 100644 --- a/res/layout/camera_controls_portrait.xml +++ b/res/layout/camera_controls_portrait.xml @@ -24,7 +24,7 @@ android:layout_marginRight="40dp" android:layout_marginEnd="40dp" android:layout_centerVertical="true" - android:src="@drawable/ic_camera_front" + android:src="@drawable/ic_switch_camera_36" android:scaleType="fitCenter" android:background="?selectableItemBackgroundBorderless" android:visibility="gone" diff --git a/res/layout/media_preview_album_rail_item.xml b/res/layout/media_preview_album_rail_item.xml deleted file mode 100644 index 3ea969c611..0000000000 --- a/res/layout/media_preview_album_rail_item.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - diff --git a/res/layout/mediarail_button_item.xml b/res/layout/mediarail_button_item.xml new file mode 100644 index 0000000000..1cdf3e6ff7 --- /dev/null +++ b/res/layout/mediarail_button_item.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/res/layout/mediarail_media_item.xml b/res/layout/mediarail_media_item.xml new file mode 100644 index 0000000000..dd24845a57 --- /dev/null +++ b/res/layout/mediarail_media_item.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + diff --git a/res/layout/mediasend_activity.xml b/res/layout/mediasend_activity.xml index f279426812..8285b524c1 100644 --- a/res/layout/mediasend_activity.xml +++ b/res/layout/mediasend_activity.xml @@ -51,4 +51,20 @@ + + \ No newline at end of file diff --git a/res/layout/mediasend_fragment.xml b/res/layout/mediasend_fragment.xml index c10b3ba854..acd59f951d 100644 --- a/res/layout/mediasend_fragment.xml +++ b/res/layout/mediasend_fragment.xml @@ -37,56 +37,41 @@ android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" + android:paddingTop="6dp" + android:paddingBottom="6dp" style="@style/Signal.Text.Body" - android:paddingTop="11dp" - android:paddingBottom="11dp" - android:drawableLeft="@drawable/ic_add_caption" - android:drawableStart="@drawable/ic_add_caption" - android:drawablePadding="6dp" android:maxLines="3" android:maxLength="240" android:hint="@string/MediaSendActivity_add_a_caption" + android:autoText="true" + android:inputType="textAutoCorrect|textCapSentences|textMultiLine" android:background="@null"/> - - + android:layout_marginBottom="12dp" + android:orientation="horizontal"> @@ -152,10 +137,9 @@ android:id="@+id/mediasend_characters_left" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="6dp" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" - android:paddingBottom="6dp" + android:paddingBottom="12dp" android:visibility="gone" tools:visibility="visible" tools:text="160/160 (1)" /> @@ -171,13 +155,4 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/layout/scribble_hud.xml b/res/layout/scribble_hud.xml index c0e42990da..36f0f08996 100644 --- a/res/layout/scribble_hud.xml +++ b/res/layout/scribble_hud.xml @@ -9,95 +9,110 @@ tools:parentTag="android.widget.LinearLayout" tools:background="@color/core_grey_60"> - + android:layout_weight="1" + android:animateLayoutChanges="true"> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - - - - - - - - - - + android:layout_marginTop="8dp" + android:layout_marginEnd="10dp" + android:layout_marginRight="10dp" + android:animateLayoutChanges="true" + android:orientation="horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:background="?attr/selectableItemBackgroundBorderless" + android:padding="8dp" + android:src="@drawable/ic_trash_outline_36" /> - - - + android:background="?attr/selectableItemBackgroundBorderless" + android:padding="8dp" + android:src="@drawable/ic_undo_36" /> + + + + + + - + + + + + \ No newline at end of file diff --git a/res/menu/mediapicker_multiselect.xml b/res/menu/mediapicker_multiselect.xml index 5757848a4e..f5dbf2503a 100644 --- a/res/menu/mediapicker_multiselect.xml +++ b/res/menu/mediapicker_multiselect.xml @@ -1,10 +1,11 @@ - + diff --git a/res/values/ids.xml b/res/values/ids.xml index 422241e3cb..cb9392f697 100644 --- a/res/values/ids.xml +++ b/res/values/ids.xml @@ -2,4 +2,5 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index 6128316348..24bf38bc3b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -462,15 +462,16 @@ Tap to select - - You can\'t share more than %d item. - You can\'t share more than %d items. - Add a caption... An item was removed because it exceeded the size limit Camera unavailable. + Message to %s + + You can\'t share more than %d item. + You can\'t share more than %d items. + All media diff --git a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java index 9939da7db7..ba4ffb73d0 100644 --- a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java +++ b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java @@ -257,10 +257,10 @@ public class AttachmentTypeSelector extends PopupWindow { private class RecentPhotoSelectedListener implements RecentPhotoViewRail.OnItemClickedListener { @Override - public void onItemClicked(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height) { + public void onItemClicked(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size) { animateWindowOutTranslate(getContentView()); - if (listener != null) listener.onQuickAttachment(uri, mimeType, bucketId, dateTaken, width, height); + if (listener != null) listener.onQuickAttachment(uri, mimeType, bucketId, dateTaken, width, height, size); } } @@ -290,7 +290,7 @@ public class AttachmentTypeSelector extends PopupWindow { public interface AttachmentClickedListener { void onClick(int type); - void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height); + void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size); } } diff --git a/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java b/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java index 4414f3541a..cc218929e7 100644 --- a/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java +++ b/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java @@ -110,6 +110,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE)); String bucketId = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_ID)); int orientation = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION)); + long size = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.SIZE)); int width = Build.VERSION.SDK_INT >= 16 ? cursor.getInt(cursor.getColumnIndexOrThrow(getWidthColumn(orientation))) : 0; int height = Build.VERSION.SDK_INT >= 16 ? cursor.getInt(cursor.getColumnIndexOrThrow(getHeightColumn(orientation))) : 0; @@ -124,7 +125,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo .into(viewHolder.imageView); viewHolder.imageView.setOnClickListener(v -> { - if (clickedListener != null) clickedListener.onItemClicked(uri, mimeType, bucketId, dateTaken, width, height); + if (clickedListener != null) clickedListener.onItemClicked(uri, mimeType, bucketId, dateTaken, width, height, size); }); } @@ -160,6 +161,6 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo } public interface OnItemClickedListener { - void onItemClicked(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height); + void onItemClicked(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size); } } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 8a91052937..62676446b7 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -2365,10 +2365,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } @Override - public void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height) { + public void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size) { linkPreviewViewModel.onUserCancel(); - // TODO: Carry over size? - Media media = new Media(uri, mimeType, dateTaken, width, height, 0, Optional.of(Media.ALL_MEDIA_BUCKET_ID), Optional.absent()); + Media media = new Media(uri, mimeType, dateTaken, width, height, size, Optional.of(Media.ALL_MEDIA_BUCKET_ID), Optional.absent()); startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER); } } diff --git a/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java b/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java index b539fc28b4..e8e2adde67 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java @@ -21,7 +21,8 @@ public class RecentPhotosLoader extends CursorLoader { MediaStore.Images.ImageColumns.DATE_MODIFIED, MediaStore.Images.ImageColumns.ORIENTATION, MediaStore.Images.ImageColumns.MIME_TYPE, - MediaStore.Images.ImageColumns.BUCKET_ID + MediaStore.Images.ImageColumns.BUCKET_ID, + MediaStore.Images.ImageColumns.SIZE }; private static final String[] PROJECTION_16 = new String[] { @@ -31,6 +32,7 @@ public class RecentPhotosLoader extends CursorLoader { MediaStore.Images.ImageColumns.ORIENTATION, MediaStore.Images.ImageColumns.MIME_TYPE, MediaStore.Images.ImageColumns.BUCKET_ID, + MediaStore.Images.ImageColumns.SIZE, MediaStore.Images.ImageColumns.WIDTH, MediaStore.Images.ImageColumns.HEIGHT }; diff --git a/src/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java b/src/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java index 11c6610883..78c06e045e 100644 --- a/src/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java +++ b/src/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.mediapreview; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; @@ -8,6 +9,7 @@ import android.view.ViewGroup; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ThumbnailView; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.util.StableIdGenerator; @@ -17,19 +19,23 @@ import java.util.List; public class MediaRailAdapter extends RecyclerView.Adapter { + private static final int TYPE_MEDIA = 1; + private static final int TYPE_BUTTON = 2; + private final GlideRequests glideRequests; private final List media; private final RailItemListener listener; - private final boolean deleteEnabled; + private final boolean editable; private final StableIdGenerator stableIdGenerator; + private RailItemAddListener addListener; private int activePosition; - public MediaRailAdapter(@NonNull GlideRequests glideRequests, @NonNull RailItemListener listener, boolean deleteEnabled) { + public MediaRailAdapter(@NonNull GlideRequests glideRequests, @NonNull RailItemListener listener, boolean editable) { this.glideRequests = glideRequests; this.media = new ArrayList<>(); this.listener = listener; - this.deleteEnabled = deleteEnabled; + this.editable = editable; this.stableIdGenerator = new StableIdGenerator<>(); setHasStableIds(true); @@ -37,13 +43,38 @@ public class MediaRailAdapter extends RecyclerView.Adapter media) { @@ -79,25 +117,45 @@ public class MediaRailAdapter extends RecyclerView.Adapter railItemListener.onRailItemClicked(distanceFromActive)); - if (deleteEnabled && isActive) { + outline.setVisibility(isActive ? View.VISIBLE : View.GONE); + + captionIndicator.setVisibility(media.getCaption().isPresent() ? View.VISIBLE : View.GONE); + + if (editable && isActive) { deleteButton.setVisibility(View.VISIBLE); deleteButton.setOnClickListener(v -> railItemListener.onRailItemDeleteClicked(distanceFromActive)); } else { @@ -111,8 +169,30 @@ public class MediaRailAdapter extends RecyclerView.Adapter addListener.onRailItemAddClicked()); + } + } + + @Override + void recycle() { + itemView.setOnClickListener(null); + } + } + public interface RailItemListener { void onRailItemClicked(int distanceFromActive); void onRailItemDeleteClicked(int distanceFromActive); } + + public interface RailItemAddListener { + void onRailItemAddClicked(); + } } diff --git a/src/org/thoughtcrime/securesms/camera/Camera1Controller.java b/src/org/thoughtcrime/securesms/mediasend/Camera1Controller.java similarity index 88% rename from src/org/thoughtcrime/securesms/camera/Camera1Controller.java rename to src/org/thoughtcrime/securesms/mediasend/Camera1Controller.java index b680f0b033..ba6530e912 100644 --- a/src/org/thoughtcrime/securesms/camera/Camera1Controller.java +++ b/src/org/thoughtcrime/securesms/mediasend/Camera1Controller.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.camera; +package org.thoughtcrime.securesms.mediasend; import android.graphics.SurfaceTexture; import android.hardware.Camera; @@ -11,21 +11,21 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -public class Camera1Controller { +class Camera1Controller { private static final String TAG = Camera1Controller.class.getSimpleName(); - private final int screenWidth; - private final int screenHeight; + private final int screenWidth; + private final int screenHeight; + private final OrderEnforcer enforcer; + private final EventListener eventListener; private Camera camera; private int cameraId; - private OrderEnforcer enforcer; - private EventListener eventListener; private SurfaceTexture previewSurface; private int screenRotation; - public Camera1Controller(int preferredDirection, int screenWidth, int screenHeight, @NonNull EventListener eventListener) { + Camera1Controller(int preferredDirection, int screenWidth, int screenHeight, @NonNull EventListener eventListener) { this.eventListener = eventListener; this.enforcer = new OrderEnforcer<>(Stage.INITIALIZED, Stage.PREVIEW_STARTED); this.cameraId = Camera.getNumberOfCameras() > 1 ? preferredDirection : Camera.CameraInfo.CAMERA_FACING_BACK; @@ -33,10 +33,11 @@ public class Camera1Controller { this.screenHeight = screenHeight; } - public void initialize() { + void initialize() { Log.d(TAG, "initialize()"); if (Camera.getNumberOfCameras() <= 0) { + Log.w(TAG, "Device doesn't have any cameras."); onCameraUnavailable(); return; } @@ -44,11 +45,13 @@ public class Camera1Controller { try { camera = Camera.open(cameraId); } catch (Exception e) { + Log.w(TAG, "Failed to open camera.", e); onCameraUnavailable(); return; } if (camera == null) { + Log.w(TAG, "Null camera instance."); onCameraUnavailable(); return; } @@ -80,9 +83,9 @@ public class Camera1Controller { eventListener.onPropertiesAvailable(getProperties()); } - public void release() { + void release() { Log.d(TAG, "release() called"); - enforcer.run(Stage.PREVIEW_STARTED, () -> { + enforcer.run(Stage.INITIALIZED, () -> { Log.d(TAG, "release() executing"); previewSurface = null; camera.stopPreview(); @@ -91,7 +94,7 @@ public class Camera1Controller { }); } - public void linkSurface(@NonNull SurfaceTexture surfaceTexture) { + void linkSurface(@NonNull SurfaceTexture surfaceTexture) { Log.d(TAG, "linkSurface() called"); enforcer.run(Stage.INITIALIZED, () -> { try { @@ -108,7 +111,7 @@ public class Camera1Controller { }); } - public void capture(@NonNull CaptureCallback callback) { + void capture(@NonNull CaptureCallback callback) { enforcer.run(Stage.PREVIEW_STARTED, () -> { camera.takePicture(null, null, null, (data, camera) -> { callback.onCaptureAvailable(data, cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); @@ -116,7 +119,7 @@ public class Camera1Controller { }); } - public int flip() { + int flip() { Log.d(TAG, "flip()"); SurfaceTexture surfaceTexture = previewSurface; cameraId = (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK; @@ -129,7 +132,7 @@ public class Camera1Controller { return cameraId; } - public void setScreenRotation(int screenRotation) { + void setScreenRotation(int screenRotation) { Log.d(TAG, "setScreenRotation(" + screenRotation + ") called"); enforcer.run(Stage.PREVIEW_STARTED, () -> { Log.d(TAG, "setScreenRotation(" + screenRotation + ") executing"); @@ -221,7 +224,7 @@ public class Camera1Controller { private final int previewWidth; private final int previewHeight; - public Properties(int cameraCount, int previewWidth, int previewHeight) { + Properties(int cameraCount, int previewWidth, int previewHeight) { this.cameraCount = cameraCount; this.previewWidth = previewWidth; this.previewHeight = previewHeight; @@ -231,11 +234,11 @@ public class Camera1Controller { return cameraCount; } - public int getPreviewWidth() { + int getPreviewWidth() { return previewWidth; } - public int getPreviewHeight() { + int getPreviewHeight() { return previewHeight; } diff --git a/src/org/thoughtcrime/securesms/camera/Camera1Fragment.java b/src/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java similarity index 91% rename from src/org/thoughtcrime/securesms/camera/Camera1Fragment.java rename to src/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java index c7fdce0e78..652f6ea308 100644 --- a/src/org/thoughtcrime/securesms/camera/Camera1Fragment.java +++ b/src/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java @@ -1,6 +1,7 @@ -package org.thoughtcrime.securesms.camera; +package org.thoughtcrime.securesms.mediasend; import android.annotation.SuppressLint; +import android.arch.lifecycle.ViewModelProviders; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Matrix; @@ -21,8 +22,11 @@ import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.RotateAnimation; import android.widget.Button; import android.widget.ImageButton; @@ -55,6 +59,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText private Controller controller; private OrderEnforcer orderEnforcer; private Camera1Controller.Properties properties; + private MediaSendViewModel viewModel; public static Camera1Fragment newInstance() { return new Camera1Fragment(); @@ -76,6 +81,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText controller = (Controller) getActivity(); camera = new Camera1Controller(TextSecurePreferences.getDirectCaptureCameraId(getContext()), displaySize.x, displaySize.y, this); orderEnforcer = new OrderEnforcer<>(Stage.SURFACE_AVAILABLE, Stage.CAMERA_PROPERTIES_AVAILABLE); + viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(requireActivity().getApplication(), new MediaRepository())).get(MediaSendViewModel.class); } @Nullable @@ -103,11 +109,22 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText @Override public void onResume() { super.onResume(); + viewModel.onCameraStarted(); camera.initialize(); + + if (cameraPreview.isAvailable()) { + orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); + } + + if (properties != null) { + orderEnforcer.markCompleted(Stage.CAMERA_PROPERTIES_AVAILABLE); + } + orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> { camera.linkSurface(cameraPreview.getSurfaceTexture()); camera.setScreenRotation(controller.getDisplayRotation()); }); + orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); @@ -118,6 +135,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText public void onPause() { super.onPause(); camera.release(); + orderEnforcer.reset(); } @Override @@ -128,6 +146,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + Log.d(TAG, "onSurfaceTextureAvailable"); orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); } @@ -159,10 +178,6 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText controller.onCameraError(); } - public void reset() { - orderEnforcer.reset(); - } - @SuppressLint("ClickableViewAccessibility") private void initControls() { flipButton = getView().findViewById(R.id.camera_flip_button); @@ -193,14 +208,14 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, () -> { if (properties.getCameraCount() > 1) { flipButton.setVisibility(properties.getCameraCount() > 1 ? View.VISIBLE : View.GONE); - flipButton.setImageResource(TextSecurePreferences.getDirectCaptureCameraId(getContext()) == Camera.CameraInfo.CAMERA_FACING_BACK ? R.drawable.ic_camera_front - : R.drawable.ic_camera_rear); flipButton.setOnClickListener(v -> { int newCameraId = camera.flip(); - flipButton.setImageResource(newCameraId == Camera.CameraInfo.CAMERA_FACING_BACK ? R.drawable.ic_camera_front - : R.drawable.ic_camera_rear); - TextSecurePreferences.setDirectCaptureCameraId(getContext(), newCameraId); + + Animation animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); + animation.setDuration(200); + animation.setInterpolator(new DecelerateInterpolator()); + flipButton.startAnimation(animation); }); } else { flipButton.setVisibility(View.GONE); @@ -209,7 +224,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText } private void onCaptureClicked() { - reset(); + orderEnforcer.reset(); Stopwatch fastCaptureTimer = new Stopwatch("Capture"); diff --git a/src/org/thoughtcrime/securesms/camera/FlipTransformation.java b/src/org/thoughtcrime/securesms/mediasend/FlipTransformation.java similarity index 95% rename from src/org/thoughtcrime/securesms/camera/FlipTransformation.java rename to src/org/thoughtcrime/securesms/mediasend/FlipTransformation.java index 3545b9fb85..d443dfefc6 100644 --- a/src/org/thoughtcrime/securesms/camera/FlipTransformation.java +++ b/src/org/thoughtcrime/securesms/mediasend/FlipTransformation.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.camera; +package org.thoughtcrime.securesms.mediasend; import android.graphics.Bitmap; import android.graphics.Canvas; diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java b/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java index 12e84eae0c..e6a90c6c16 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java @@ -93,6 +93,7 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo public void onResume() { super.onResume(); + viewModel.onFolderPickerStarted(); requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java b/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java index 0c40542a17..ffa3d5f615 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java @@ -4,33 +4,27 @@ import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; -import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; -import android.support.v7.view.ActionMode; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.Window; import android.view.WindowManager; import android.widget.Toast; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; @@ -119,25 +113,27 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem public void onResume() { super.onResume(); + viewModel.onItemPickerStarted(); requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } @Override public void onPrepareOptionsMenu(Menu menu) { - requireActivity().getMenuInflater().inflate(R.menu.mediapicker_default, menu); - - MenuItem beginSelectionButton = menu.findItem(R.id.mediapicker_menu_add); - - beginSelectionButton.setVisible(!viewModel.getCountButtonState().getValue().getVisibility()); + if (viewModel.getCountButtonState().getValue() != null && viewModel.getCountButtonState().getValue().isVisible()) { + requireActivity().getMenuInflater().inflate(R.menu.mediapicker_multiselect, menu); + } else { + requireActivity().getMenuInflater().inflate(R.menu.mediapicker_default, menu); + } } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.mediapicker_menu_add) { - adapter.setForcedMultiSelect(true); - viewModel.onMultiSelectStarted(); - return true; + switch (item.getItemId()) { + case R.id.mediapicker_menu_add: + adapter.setForcedMultiSelect(true); + viewModel.onMultiSelectStarted(); + return true; } return false; } @@ -162,7 +158,7 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem @Override public void onMediaSelectionOverflow(int maxSelection) { - Toast.makeText(requireContext(), getResources().getQuantityString(R.plurals.MediaPickerItemFragment_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), getResources().getQuantityString(R.plurals.MediaSendActivity_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show(); } private void initToolbar(Toolbar toolbar) { diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 507dcda862..a5aa3d4a35 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.mediasend; +import android.Manifest; import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; @@ -19,10 +20,10 @@ import android.widget.Toast; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.TransportOption; -import org.thoughtcrime.securesms.camera.Camera1Fragment; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.MediaConstraints; +import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.scribbles.ScribbleFragment; @@ -58,8 +59,6 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple public static final String EXTRA_MESSAGE = "message"; public static final String EXTRA_TRANSPORT = "transport"; - private static final int MAX_PUSH = 32; - private static final int MAX_SMS = 1; private static final String KEY_ADDRESS = "address"; private static final String KEY_BODY = "body"; @@ -81,6 +80,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple private View countButton; private TextView countButtonText; + private View cameraButton; /** * Get an intent to launch the media send flow starting with the picker. @@ -134,14 +134,13 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple countButton = findViewById(R.id.mediasend_count_button); countButtonText = findViewById(R.id.mediasend_count_button_text); + cameraButton = findViewById(R.id.mediasend_camera_button); viewModel = ViewModelProviders.of(this, new MediaSendViewModel.Factory(getApplication(), new MediaRepository())).get(MediaSendViewModel.class); recipient = Recipient.from(this, Address.fromSerialized(getIntent().getStringExtra(KEY_ADDRESS)), true); transport = getIntent().getParcelableExtra(KEY_TRANSPORT); - viewModel.setMediaConstraints(transport.isSms() ? MediaConstraints.getMmsMediaConstraints(transport.getSimSubscriptionId().or(-1)) - : MediaConstraints.getPushMediaConstraints()); - + viewModel.setTransport(transport); viewModel.onBodyChanged(getIntent().getStringExtra(KEY_BODY)); List media = getIntent().getParcelableArrayListExtra(KEY_MEDIA); @@ -156,7 +155,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple } else if (!Util.isEmpty(media)) { viewModel.onSelectedMediaChanged(this, media); - Fragment fragment = MediaSendFragment.newInstance(transport, dynamicLanguage.getCurrentLocale()); + Fragment fragment = MediaSendFragment.newInstance(recipient, transport, dynamicLanguage.getCurrentLocale()); getSupportFragmentManager().beginTransaction() .replace(R.id.mediasend_fragment_container, fragment, TAG_SEND) .commit(); @@ -168,6 +167,18 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple } initializeCountButtonObserver(transport, dynamicLanguage.getCurrentLocale()); + initializeCameraButtonObserver(); + initializeErrorObserver(); + + cameraButton.setOnClickListener(v -> { + int maxSelection = viewModel.getMaxSelection(); + + if (viewModel.getSelectedMedia().getValue() != null && viewModel.getSelectedMedia().getValue().size() >= maxSelection) { + Toast.makeText(this, getResources().getQuantityString(R.plurals.MediaSendActivity_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show(); + } else { + navigateToCamera(); + } + }); } @Override @@ -189,11 +200,16 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple } } + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); + } + @Override public void onFolderSelected(@NonNull MediaFolder folder) { viewModel.onFolderSelected(folder.getBucketId()); - MediaPickerItemFragment fragment = MediaPickerItemFragment.newInstance(folder.getBucketId(), folder.getTitle(), transport.isSms() ? MAX_SMS :MAX_PUSH); + MediaPickerItemFragment fragment = MediaPickerItemFragment.newInstance(folder.getBucketId(), folder.getTitle(), viewModel.getMaxSelection()); getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right) .replace(R.id.mediasend_fragment_container, fragment, TAG_ITEM_PICKER) @@ -203,14 +219,14 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple @Override public void onMediaSelected(@NonNull String bucketId) { - navigateToMediaSend(transport, dynamicLanguage.getCurrentLocale()); + navigateToMediaSend(recipient, transport, dynamicLanguage.getCurrentLocale()); } @Override public void onAddMediaClicked(@NonNull String bucketId) { // TODO: Get actual folder title somehow MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(recipient); - MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId, "", transport.isSms() ? MAX_SMS : MAX_PUSH); + MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId, "", viewModel.getMaxSelection()); getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.stationary, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right) @@ -302,7 +318,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple Log.i(TAG, "Camera capture stored: " + media.getUri().toString()); viewModel.onImageCaptured(media); - navigateToMediaSend(transport, dynamicLanguage.getCurrentLocale()); + navigateToMediaSend(recipient, transport, dynamicLanguage.getCurrentLocale()); }); } @@ -316,26 +332,42 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple if (buttonState == null) return; countButtonText.setText(String.valueOf(buttonState.getCount())); - countButton.setEnabled(buttonState.getVisibility()); - animateCountButtonVisibility(countButton, countButton.getVisibility(), buttonState.getVisibility() ? View.VISIBLE : View.GONE); + countButton.setEnabled(buttonState.isVisible()); + animateButtonVisibility(countButton, countButton.getVisibility(), buttonState.isVisible() ? View.VISIBLE : View.GONE); if (buttonState.getCount() > 0) { - countButton.setOnClickListener(v -> { - Camera1Fragment fragment = (Camera1Fragment) getSupportFragmentManager().findFragmentByTag(TAG_CAMERA); - if (fragment != null) { - fragment.reset(); - } - - navigateToMediaSend(transport, locale); - }); + countButton.setOnClickListener(v -> navigateToMediaSend(recipient, transport, locale)); } else { countButton.setOnClickListener(null); } }); } - private void navigateToMediaSend(@NonNull TransportOption transport, @NonNull Locale locale) { - MediaSendFragment fragment = MediaSendFragment.newInstance(transport, locale); + private void initializeCameraButtonObserver() { + viewModel.getCameraButtonVisibility().observe(this, visible -> { + if (visible == null) return; + animateButtonVisibility(cameraButton, cameraButton.getVisibility(), visible ? View.VISIBLE : View.GONE); + }); + } + + private void initializeErrorObserver() { + viewModel.getError().observe(this, error -> { + if (error == null) return; + + switch (error) { + case ITEM_TOO_LARGE: + Toast.makeText(this, R.string.MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit, Toast.LENGTH_LONG).show(); + break; + case TOO_MANY_ITEMS: + int maxSelection = viewModel.getMaxSelection(); + Toast.makeText(this, getResources().getQuantityString(R.plurals.MediaSendActivity_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show(); + break; + } + }); + } + + private void navigateToMediaSend(@NonNull Recipient recipient, @NonNull TransportOption transport, @NonNull Locale locale) { + MediaSendFragment fragment = MediaSendFragment.newInstance(recipient, transport, locale); String backstackTag = null; if (getSupportFragmentManager().findFragmentByTag(TAG_SEND) != null) { @@ -350,19 +382,44 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple .commit(); } - private void animateCountButtonVisibility(View countButton, int oldVisibility, int newVisibility) { + private void navigateToCamera() { + Permissions.with(this) + .request(Manifest.permission.CAMERA) + .ifNecessary() + .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_photo_camera_white_48dp) + .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) + .onAllGranted(() -> { + Camera1Fragment fragment = getOrCreateCameraFragment(); + getSupportFragmentManager().beginTransaction() + .setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right) + .replace(R.id.mediasend_fragment_container, fragment, TAG_CAMERA) + .addToBackStack(null) + .commit(); + }) + .onAnyDenied(() -> Toast.makeText(MediaSendActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) + .execute(); + } + + private Camera1Fragment getOrCreateCameraFragment() { + Camera1Fragment fragment = (Camera1Fragment) getSupportFragmentManager().findFragmentByTag(TAG_CAMERA); + + return fragment != null ? fragment + : Camera1Fragment.newInstance(); + } + + private void animateButtonVisibility(View button, int oldVisibility, int newVisibility) { if (oldVisibility == newVisibility) return; - if (countButton.getAnimation() != null) { - countButton.getAnimation().cancel(); - countButton.setVisibility(newVisibility); + if (button.getAnimation() != null) { + button.getAnimation().cancel(); + button.setVisibility(newVisibility); } else if (newVisibility == View.VISIBLE) { - countButton.setVisibility(View.VISIBLE); + button.setVisibility(View.VISIBLE); Animation animation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animation.setDuration(250); animation.setInterpolator(new OvershootInterpolator()); - countButton.startAnimation(animation); + button.startAnimation(animation); } else { Animation animation = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animation.setDuration(150); @@ -370,12 +427,11 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple animation.setAnimationListener(new SimpleAnimationListener() { @Override public void onAnimationEnd(Animation animation) { - countButton.setVisibility(View.GONE); + button.setVisibility(View.GONE); } }); - countButton.startAnimation(animation); + button.startAnimation(animation); } - } } diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java index 6b5f24e536..7588dd92de 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.scribbles.widget.ScribbleView; import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState; import org.thoughtcrime.securesms.util.MediaUtil; @@ -51,6 +52,7 @@ import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.views.Stub; +import org.whispersystems.libsignal.util.guava.Optional; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -72,12 +74,12 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl private static final String TAG = MediaSendFragment.class.getSimpleName(); + private static final String KEY_ADDRESS = "address"; private static final String KEY_TRANSPORT = "transport"; private static final String KEY_LOCALE = "locale"; private InputAwareLayout hud; private SendButton sendButton; - private View addButton; private ComposeText composeText; private ViewGroup composeContainer; private EmojiEditText captionText; @@ -98,8 +100,9 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl private final Rect visibleBounds = new Rect(); - public static MediaSendFragment newInstance(@NonNull TransportOption transport, @NonNull Locale locale) { + public static MediaSendFragment newInstance(@NonNull Recipient recipient, @NonNull TransportOption transport, @NonNull Locale locale) { Bundle args = new Bundle(); + args.putParcelable(KEY_ADDRESS, recipient.getAddress()); args.putParcelable(KEY_TRANSPORT, transport); args.putSerializable(KEY_LOCALE, locale); @@ -145,7 +148,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl emojiDrawer = new Stub<>(view.findViewById(R.id.mediasend_emoji_drawer_stub)); fragmentPager = view.findViewById(R.id.mediasend_pager); mediaRail = view.findViewById(R.id.mediasend_media_rail); - addButton = view.findViewById(R.id.mediasend_add_button); playbackControlsContainer = view.findViewById(R.id.mediasend_playback_controls_container); charactersLeft = view.findViewById(R.id.mediasend_characters_left); @@ -205,6 +207,11 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl composeText.append(viewModel.getBody()); + Recipient recipient = Recipient.from(requireContext(), getArguments().getParcelable(KEY_ADDRESS), false); + String displayName = Optional.fromNullable(recipient.getName()) + .or(Optional.fromNullable(recipient.getProfileName()) + .or(recipient.getAddress().serialize())); + composeText.setHint(getString(R.string.MediaSendActivity_message_to_s, displayName), null); if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) { emojiToggle.setVisibility(View.GONE); @@ -264,12 +271,24 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl @Override public void onKeyboardShown() { - if (composeText.hasFocus()) { + if (sendButton.getSelectedTransport().isSms()) { + mediaRail.setVisibility(View.GONE); composeContainer.setVisibility(View.VISIBLE); captionText.setVisibility(View.GONE); - } else if (captionText.hasFocus()) { - mediaRail.setVisibility(View.GONE); - composeContainer.setVisibility(View.GONE); + } else { + if (captionText.hasFocus()) { + mediaRail.setVisibility(View.VISIBLE); + composeContainer.setVisibility(View.GONE); + captionText.setVisibility(View.VISIBLE); + } else if (composeText.hasFocus()) { + mediaRail.setVisibility(View.VISIBLE); + composeContainer.setVisibility(View.VISIBLE); + captionText.setVisibility(View.GONE); + } else { + mediaRail.setVisibility(View.GONE); + composeContainer.setVisibility(View.VISIBLE); + captionText.setVisibility(View.GONE); + } } } @@ -277,9 +296,15 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl public void onKeyboardHidden() { composeContainer.setVisibility(View.VISIBLE); - if (!Util.isEmpty(viewModel.getSelectedMedia().getValue()) && viewModel.getSelectedMedia().getValue().size() > 1) { + if (sendButton.getSelectedTransport().isSms()) { + mediaRail.setVisibility(View.GONE); + captionText.setVisibility(View.GONE); + } else { mediaRail.setVisibility(View.VISIBLE); - captionText.setVisibility(View.VISIBLE); + + if (!Util.isEmpty(viewModel.getSelectedMedia().getValue()) && viewModel.getSelectedMedia().getValue().size() > 1) { + captionText.setVisibility(View.VISIBLE); + } } } @@ -306,7 +331,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl fragmentPagerAdapter.setMedia(media); - mediaRail.setVisibility(media.size() > 1 ? View.VISIBLE : View.GONE); + mediaRail.setVisibility(sendButton.getSelectedTransport().isSms() ? View.GONE : View.VISIBLE); captionText.setVisibility((media.size() > 1 || media.get(0).getCaption().isPresent()) ? View.VISIBLE : View.GONE); mediaRailAdapter.setMedia(media); }); @@ -318,7 +343,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl mediaRailAdapter.setActivePosition(position); mediaRail.smoothScrollToPosition(position); - if (!fragmentPagerAdapter.getAllMedia().isEmpty()) { + if (fragmentPagerAdapter.getAllMedia().size() > position) { captionText.setText(fragmentPagerAdapter.getAllMedia().get(position).getCaption().or("")); } @@ -337,18 +362,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl viewModel.getBucketId().observe(this, bucketId -> { if (bucketId == null) return; - if (sendButton.getSelectedTransport().isSms()) { - addButton.setVisibility(View.GONE); - } else { - addButton.setVisibility(View.VISIBLE); - addButton.setOnClickListener(v -> controller.onAddMediaClicked(bucketId)); - } - }); - - viewModel.getError().observe(this, error -> { - if (error == MediaSendViewModel.Error.ITEM_TOO_LARGE) { - Toast.makeText(requireContext(), R.string.MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit, Toast.LENGTH_LONG).show(); - } + mediaRailAdapter.setAddButtonListener(() -> controller.onAddMediaClicked(bucketId)); }); } diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java index b5c2417147..a4c674461d 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java @@ -12,6 +12,7 @@ import android.text.TextUtils; import com.annimon.stream.Stream; +import org.thoughtcrime.securesms.TransportOption; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.util.MediaUtil; @@ -30,6 +31,9 @@ import java.util.Map; */ class MediaSendViewModel extends ViewModel { + private static final int MAX_PUSH = 32; + private static final int MAX_SMS = 1; + private final Application application; private final MediaRepository repository; private final MutableLiveData> selectedMedia; @@ -38,6 +42,7 @@ class MediaSendViewModel extends ViewModel { private final MutableLiveData bucketId; private final MutableLiveData> folders; private final MutableLiveData countButtonState; + private final MutableLiveData cameraButtonVisibility; private final SingleLiveEvent error; private final Map savedDrawState; @@ -46,27 +51,37 @@ class MediaSendViewModel extends ViewModel { private CountButtonState.Visibility countButtonVisibility; private boolean sentMedia; private Optional lastImageCapture; + private int maxSelection; private MediaSendViewModel(@NonNull Application application, @NonNull MediaRepository repository) { - this.application = application; - this.repository = repository; - this.selectedMedia = new MutableLiveData<>(); - this.bucketMedia = new MutableLiveData<>(); - this.position = new MutableLiveData<>(); - this.bucketId = new MutableLiveData<>(); - this.folders = new MutableLiveData<>(); - this.countButtonState = new MutableLiveData<>(); - this.error = new SingleLiveEvent<>(); - this.savedDrawState = new HashMap<>(); - this.countButtonVisibility = CountButtonState.Visibility.CONDITIONAL; - this.lastImageCapture = Optional.absent(); + this.application = application; + this.repository = repository; + this.selectedMedia = new MutableLiveData<>(); + this.bucketMedia = new MutableLiveData<>(); + this.position = new MutableLiveData<>(); + this.bucketId = new MutableLiveData<>(); + this.folders = new MutableLiveData<>(); + this.countButtonState = new MutableLiveData<>(); + this.cameraButtonVisibility = new MutableLiveData<>(); + this.error = new SingleLiveEvent<>(); + this.savedDrawState = new HashMap<>(); + this.countButtonVisibility = CountButtonState.Visibility.CONDITIONAL; + this.lastImageCapture = Optional.absent(); + this.body = ""; position.setValue(-1); countButtonState.setValue(new CountButtonState(0, CountButtonState.Visibility.CONDITIONAL)); + cameraButtonVisibility.setValue(false); } - void setMediaConstraints(@NonNull MediaConstraints mediaConstraints) { - this.mediaConstraints = mediaConstraints; + void setTransport(@NonNull TransportOption transport) { + if (transport.isSms()) { + maxSelection = MAX_SMS; + mediaConstraints = MediaConstraints.getMmsMediaConstraints(transport.getSimSubscriptionId().or(-1)); + } else { + maxSelection = MAX_PUSH; + mediaConstraints = MediaConstraints.getPushMediaConstraints(); + } } void onSelectedMediaChanged(@NonNull Context context, @NonNull List newMedia) { @@ -75,13 +90,16 @@ class MediaSendViewModel extends ViewModel { if (filteredMedia.size() != newMedia.size()) { error.postValue(Error.ITEM_TOO_LARGE); + } else if (filteredMedia.size() > maxSelection) { + filteredMedia = filteredMedia.subList(0, maxSelection); + error.postValue(Error.TOO_MANY_ITEMS); } if (filteredMedia.size() > 0) { String computedId = Stream.of(filteredMedia) .skip(1) - .reduce(filteredMedia.get(0).getBucketId().orNull(), (id, m) -> { - if (Util.equals(id, m.getBucketId().orNull())) { + .reduce(filteredMedia.get(0).getBucketId().or(Media.ALL_MEDIA_BUCKET_ID), (id, m) -> { + if (Util.equals(id, m.getBucketId().or(Media.ALL_MEDIA_BUCKET_ID))) { return id; } else { return Media.ALL_MEDIA_BUCKET_ID; @@ -106,6 +124,7 @@ class MediaSendViewModel extends ViewModel { void onImageEditorStarted() { countButtonVisibility = CountButtonState.Visibility.FORCED_OFF; countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility)); + cameraButtonVisibility.setValue(false); } void onImageEditorEnded() { @@ -113,6 +132,18 @@ class MediaSendViewModel extends ViewModel { countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility)); } + void onCameraStarted() { + cameraButtonVisibility.setValue(false); + } + + void onItemPickerStarted() { + cameraButtonVisibility.setValue(true); + } + + void onFolderPickerStarted() { + cameraButtonVisibility.setValue(true); + } + void onBodyChanged(@NonNull CharSequence body) { this.body = body; } @@ -143,6 +174,11 @@ class MediaSendViewModel extends ViewModel { selected = new LinkedList<>(); } + if (selected.size() >= maxSelection) { + error.postValue(Error.TOO_MANY_ITEMS); + return; + } + lastImageCapture = Optional.of(media); selected.add(media); @@ -208,22 +244,30 @@ class MediaSendViewModel extends ViewModel { return countButtonState; } - CharSequence getBody() { + @NonNull LiveData getCameraButtonVisibility() { + return cameraButtonVisibility; + } + + @NonNull CharSequence getBody() { return body; } - LiveData getPosition() { + @NonNull LiveData getPosition() { return position; } - LiveData getBucketId() { + @NonNull LiveData getBucketId() { return bucketId; } - LiveData getError() { + @NonNull LiveData getError() { return error; } + int getMaxSelection() { + return maxSelection; + } + private @NonNull List getSelectedMediaOrDefault() { return selectedMedia.getValue() == null ? Collections.emptyList() : selectedMedia.getValue(); @@ -252,7 +296,7 @@ class MediaSendViewModel extends ViewModel { } enum Error { - ITEM_TOO_LARGE + ITEM_TOO_LARGE, TOO_MANY_ITEMS } static class CountButtonState { @@ -268,7 +312,7 @@ class MediaSendViewModel extends ViewModel { return count; } - boolean getVisibility() { + boolean isVisible() { switch (visibility) { case FORCED_ON: return true; case FORCED_OFF: return false; diff --git a/src/org/thoughtcrime/securesms/camera/OrderEnforcer.java b/src/org/thoughtcrime/securesms/mediasend/OrderEnforcer.java similarity index 62% rename from src/org/thoughtcrime/securesms/camera/OrderEnforcer.java rename to src/org/thoughtcrime/securesms/mediasend/OrderEnforcer.java index fd7cc2b9b1..6851dd97a4 100644 --- a/src/org/thoughtcrime/securesms/camera/OrderEnforcer.java +++ b/src/org/thoughtcrime/securesms/mediasend/OrderEnforcer.java @@ -1,11 +1,11 @@ -package org.thoughtcrime.securesms.camera; +package org.thoughtcrime.securesms.mediasend; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Stack; @SuppressWarnings("ConstantConditions") public class OrderEnforcer { @@ -22,21 +22,20 @@ public class OrderEnforcer { if (isCompletedThrough(stage)) { r.run(); } else { - stages.get(stage).getActions().add(r); + stages.get(stage).addAction(r); } } public synchronized void markCompleted(@NonNull E stage) { - stages.get(stage).setCompleted(true); + stages.get(stage).markCompleted(); for (E s : stages.keySet()) { StageDetails details = stages.get(s); if (details.isCompleted()) { - for (Runnable r : details.getActions()) { - r.run(); + while (details.hasAction()) { + details.popAction().run(); } - details.getActions().clear(); } else { break; } @@ -45,8 +44,7 @@ public class OrderEnforcer { public synchronized void reset() { for (StageDetails details : stages.values()) { - details.setCompleted(false); - details.getActions().clear(); + details.reset(); } } @@ -62,19 +60,32 @@ public class OrderEnforcer { } private static class StageDetails { - private boolean completed = false; - private List actions = new CopyOnWriteArrayList<>(); + private boolean completed = false; + private Stack actions = new Stack<>(); - @NonNull List getActions() { - return actions; + boolean hasAction() { + return !actions.isEmpty(); + } + + @Nullable Runnable popAction() { + return actions.pop(); + } + + void addAction(@NonNull Runnable runnable) { + actions.push(runnable); + } + + void reset() { + actions.clear(); + completed = false; } boolean isCompleted() { return completed; } - void setCompleted(boolean completed) { - this.completed = completed; + void markCompleted() { + completed = true; } } } diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java index e16d33b063..cd8c0fdeb3 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java +++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java @@ -41,11 +41,10 @@ import java.util.Set; public class ScribbleHud extends InputAwareLayout implements ViewTreeObserver.OnGlobalLayoutListener { private View drawButton; - private View highlightButton; private View textButton; - private View stickerButton; private View undoButton; private View deleteButton; + private View confirmButton; private View saveButton; private VerticalSlideColorPicker colorPicker; private RecyclerView colorPalette; @@ -111,11 +110,10 @@ public class ScribbleHud extends InputAwareLayout implements ViewTreeObserver.On setOrientation(VERTICAL); drawButton = findViewById(R.id.scribble_draw_button); - highlightButton = findViewById(R.id.scribble_highlight_button); textButton = findViewById(R.id.scribble_text_button); - stickerButton = findViewById(R.id.scribble_sticker_button); undoButton = findViewById(R.id.scribble_undo_button); deleteButton = findViewById(R.id.scribble_delete_button); + confirmButton = findViewById(R.id.scribble_confirm_button); saveButton = findViewById(R.id.scribble_save_button); colorPicker = findViewById(R.id.scribble_color_picker); colorPalette = findViewById(R.id.scribble_color_palette); @@ -163,6 +161,8 @@ public class ScribbleHud extends InputAwareLayout implements ViewTreeObserver.On setMode(Mode.NONE); }); + confirmButton.setOnClickListener(v -> setMode(Mode.NONE)); + sendButton.addOnTransportChangedListener((newTransport, manuallySelected) -> { presentCharactersRemaining(); composeText.setTransport(newTransport); @@ -255,84 +255,70 @@ public class ScribbleHud extends InputAwareLayout implements ViewTreeObserver.On private void presentModeNone() { drawButton.setVisibility(VISIBLE); - highlightButton.setVisibility(VISIBLE); textButton.setVisibility(VISIBLE); - stickerButton.setVisibility(VISIBLE); undoButton.setVisibility(GONE); deleteButton.setVisibility(GONE); + confirmButton.setVisibility(GONE); colorPicker.setVisibility(GONE); colorPalette.setVisibility(GONE); drawButton.setOnClickListener(v -> setMode(Mode.DRAW)); - highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT)); textButton.setOnClickListener(v -> setMode(Mode.TEXT)); - stickerButton.setOnClickListener(v -> setMode(Mode.STICKER)); } private void presentModeDraw() { - drawButton.setVisibility(VISIBLE); + confirmButton.setVisibility(VISIBLE); undoButton.setVisibility(VISIBLE); colorPicker.setVisibility(VISIBLE); colorPalette.setVisibility(VISIBLE); - highlightButton.setVisibility(GONE); + drawButton.setVisibility(GONE); textButton.setVisibility(GONE); - stickerButton.setVisibility(GONE); deleteButton.setVisibility(GONE); - drawButton.setOnClickListener(v -> setMode(Mode.NONE)); colorPicker.setOnColorChangeListener(standardOnColorChangeListener); colorPicker.setActiveColor(Color.RED); } private void presentModeHighlight() { - highlightButton.setVisibility(VISIBLE); + confirmButton.setVisibility(VISIBLE); undoButton.setVisibility(VISIBLE); colorPicker.setVisibility(VISIBLE); colorPalette.setVisibility(VISIBLE); drawButton.setVisibility(GONE); textButton.setVisibility(GONE); - stickerButton.setVisibility(GONE); deleteButton.setVisibility(GONE); - highlightButton.setOnClickListener(v -> setMode(Mode.NONE)); - colorPicker.setOnColorChangeListener(highlightOnColorChangeListener); colorPicker.setActiveColor(Color.YELLOW); } private void presentModeText() { - textButton.setVisibility(VISIBLE); + confirmButton.setVisibility(VISIBLE); deleteButton.setVisibility(VISIBLE); colorPicker.setVisibility(VISIBLE); colorPalette.setVisibility(VISIBLE); + textButton.setVisibility(GONE); drawButton.setVisibility(GONE); - highlightButton.setVisibility(GONE); - stickerButton.setVisibility(GONE); undoButton.setVisibility(GONE); - textButton.setOnClickListener(v -> setMode(Mode.NONE)); - colorPicker.setOnColorChangeListener(standardOnColorChangeListener); colorPicker.setActiveColor(Color.WHITE); } private void presentModeSticker() { - stickerButton.setVisibility(VISIBLE); deleteButton.setVisibility(VISIBLE); + confirmButton.setVisibility(VISIBLE); drawButton.setVisibility(GONE); - highlightButton.setVisibility(GONE); textButton.setVisibility(GONE); undoButton.setVisibility(GONE); colorPicker.setVisibility(GONE); colorPalette.setVisibility(GONE); - - stickerButton.setOnClickListener(v -> setMode(Mode.NONE)); } private void presentCharactersRemaining() { diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java b/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java index 3c4fe80968..e33dbd00b6 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java @@ -136,6 +136,7 @@ public class MotionView extends FrameLayout implements TextWatcher { this.addView(editText); this.editText.clearFocus(); this.editText.addTextChangedListener(this); + this.editText.setId(R.id.motion_view_edittext); // init listeners this.scaleGestureDetector = new ScaleGestureDetector(context, new ScaleListener()); diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java b/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java index 497a830b1c..27071eae7b 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java @@ -43,7 +43,7 @@ import org.thoughtcrime.securesms.R; public class VerticalSlideColorPicker extends View { - private static final float INDICATOR_TO_BAR_WIDTH_RATIO = 0.8f; + private static final float INDICATOR_TO_BAR_WIDTH_RATIO = 0.5f; private Paint paint; private Paint strokePaint; @@ -131,9 +131,9 @@ public class VerticalSlideColorPicker extends View { protected void onDraw(Canvas canvas) { super.onDraw(canvas); - path.addCircle(centerX, borderWidth + colorPickerRadius, colorPickerRadius, Path.Direction.CW); + path.addCircle(centerX, borderWidth + colorPickerRadius + indicatorRadius, colorPickerRadius, Path.Direction.CW); path.addRect(colorPickerBody, Path.Direction.CW); - path.addCircle(centerX, viewHeight - (borderWidth + colorPickerRadius), colorPickerRadius, Path.Direction.CW); + path.addCircle(centerX, viewHeight - (borderWidth + colorPickerRadius + indicatorRadius), colorPickerRadius, Path.Direction.CW); bitmapCanvas.drawColor(Color.TRANSPARENT); @@ -178,7 +178,10 @@ public class VerticalSlideColorPicker extends View { indicatorRadius = (viewWidth / 2) - borderWidth; colorPickerRadius = (barWidth / 2) - borderWidth; - colorPickerBody = new RectF(centerX - colorPickerRadius, borderWidth + colorPickerRadius, centerX + colorPickerRadius, viewHeight - (borderWidth + colorPickerRadius)); + colorPickerBody = new RectF(centerX - colorPickerRadius, + borderWidth + colorPickerRadius + indicatorRadius, + centerX + colorPickerRadius, + viewHeight - (borderWidth + colorPickerRadius + indicatorRadius)); LinearGradient gradient = new LinearGradient(0, colorPickerBody.top, 0, colorPickerBody.bottom, colors, null, Shader.TileMode.CLAMP); paint.setShader(gradient); diff --git a/test/unitTest/java/org/thoughtcrime/securesms/camera/OrderEnforcerTest.java b/test/unitTest/java/org/thoughtcrime/securesms/camera/OrderEnforcerTest.java index bef016c92f..fece0f7805 100644 --- a/test/unitTest/java/org/thoughtcrime/securesms/camera/OrderEnforcerTest.java +++ b/test/unitTest/java/org/thoughtcrime/securesms/camera/OrderEnforcerTest.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.camera; import org.junit.Test; +import org.thoughtcrime.securesms.mediasend.OrderEnforcer; import java.util.concurrent.atomic.AtomicInteger;