twterm 1.3.0 → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -18
  3. data/bin/twterm +1 -1
  4. data/lib/twterm/app.rb +120 -30
  5. data/lib/twterm/client.rb +10 -13
  6. data/lib/twterm/completion_mamanger.rb +11 -6
  7. data/lib/twterm/direct_message.rb +6 -28
  8. data/lib/twterm/direct_message_composer.rb +10 -5
  9. data/lib/twterm/direct_message_manager.rb +5 -6
  10. data/lib/twterm/event/notification/abstract_notification.rb +27 -0
  11. data/lib/twterm/event/notification/error.rb +13 -0
  12. data/lib/twterm/event/notification/info.rb +13 -0
  13. data/lib/twterm/event/notification/success.rb +13 -0
  14. data/lib/twterm/event/notification/warning.rb +13 -0
  15. data/lib/twterm/event_dispatcher.rb +1 -1
  16. data/lib/twterm/extensions/array.rb +5 -0
  17. data/lib/twterm/extensions/enumerator/lazy.rb +3 -0
  18. data/lib/twterm/extensions/string.rb +0 -4
  19. data/lib/twterm/friendship.rb +1 -85
  20. data/lib/twterm/image/between.rb +31 -0
  21. data/lib/twterm/image/blank_line.rb +21 -0
  22. data/lib/twterm/image/bold.rb +31 -0
  23. data/lib/twterm/image/brackets.rb +21 -0
  24. data/lib/twterm/image/color.rb +45 -0
  25. data/lib/twterm/image/empty.rb +21 -0
  26. data/lib/twterm/image/horizontal_sequential_image.rb +48 -0
  27. data/lib/twterm/image/parens.rb +21 -0
  28. data/lib/twterm/image/string_image.rb +38 -0
  29. data/lib/twterm/image/vertical_sequential_image.rb +43 -0
  30. data/lib/twterm/image.rb +107 -0
  31. data/lib/twterm/key_mapper/abstract_key_mapper.rb +51 -0
  32. data/lib/twterm/key_mapper/app_key_mapper.rb +13 -0
  33. data/lib/twterm/key_mapper/cursor_key_mapper.rb +13 -0
  34. data/lib/twterm/key_mapper/general_key_mapper.rb +18 -0
  35. data/lib/twterm/key_mapper/no_such_command.rb +20 -0
  36. data/lib/twterm/key_mapper/no_such_key.rb +16 -0
  37. data/lib/twterm/key_mapper/status_key_mapper.rb +18 -0
  38. data/lib/twterm/key_mapper/tab_key_mapper.rb +31 -0
  39. data/lib/twterm/key_mapper.rb +127 -0
  40. data/lib/twterm/list.rb +0 -31
  41. data/lib/twterm/notifier.rb +7 -7
  42. data/lib/twterm/repository/abstract_entity_repository.rb +41 -0
  43. data/lib/twterm/repository/abstract_expirable_entity_repository.rb +35 -0
  44. data/lib/twterm/repository/abstract_repository.rb +64 -0
  45. data/lib/twterm/repository/direct_message_repository.rb +14 -0
  46. data/lib/twterm/repository/friendship_repository.rb +108 -0
  47. data/lib/twterm/repository/hashtag_repository.rb +39 -0
  48. data/lib/twterm/repository/list_repository.rb +14 -0
  49. data/lib/twterm/repository/status_repository.rb +36 -0
  50. data/lib/twterm/repository/user_repository.rb +22 -0
  51. data/lib/twterm/rest_client.rb +107 -63
  52. data/lib/twterm/screen.rb +21 -15
  53. data/lib/twterm/search_query_window.rb +139 -0
  54. data/lib/twterm/status.rb +14 -108
  55. data/lib/twterm/streaming_client.rb +13 -12
  56. data/lib/twterm/tab/base.rb +48 -8
  57. data/lib/twterm/tab/direct_message/conversation.rb +53 -52
  58. data/lib/twterm/tab/direct_message/conversation_list.rb +46 -45
  59. data/lib/twterm/tab/dumpable.rb +3 -3
  60. data/lib/twterm/tab/key_assignments_cheatsheet.rb +58 -57
  61. data/lib/twterm/tab/loadable.rb +20 -0
  62. data/lib/twterm/tab/new/list.rb +32 -43
  63. data/lib/twterm/tab/new/search.rb +31 -44
  64. data/lib/twterm/tab/new/start.rb +44 -55
  65. data/lib/twterm/tab/new/user.rb +15 -12
  66. data/lib/twterm/tab/rate_limit_status.rb +84 -0
  67. data/lib/twterm/tab/scrollable.rb +39 -19
  68. data/lib/twterm/tab/searchable.rb +133 -0
  69. data/lib/twterm/tab/statuses/base.rb +139 -129
  70. data/lib/twterm/tab/statuses/conversation.rb +26 -15
  71. data/lib/twterm/tab/statuses/favorites.rb +10 -8
  72. data/lib/twterm/tab/statuses/home.rb +10 -9
  73. data/lib/twterm/tab/statuses/list_timeline.rb +12 -8
  74. data/lib/twterm/tab/statuses/mentions.rb +17 -11
  75. data/lib/twterm/tab/statuses/search.rb +8 -5
  76. data/lib/twterm/tab/statuses/user_timeline.rb +11 -8
  77. data/lib/twterm/tab/user_list_management.rb +109 -0
  78. data/lib/twterm/tab/user_tab.rb +125 -126
  79. data/lib/twterm/tab/users/base.rb +39 -41
  80. data/lib/twterm/tab/users/followers.rb +9 -6
  81. data/lib/twterm/tab/users/friends.rb +9 -6
  82. data/lib/twterm/tab_manager.rb +64 -40
  83. data/lib/twterm/tweetbox.rb +18 -13
  84. data/lib/twterm/uri_opener.rb +2 -1
  85. data/lib/twterm/user.rb +2 -110
  86. data/lib/twterm/version.rb +1 -1
  87. data/lib/twterm/view.rb +30 -0
  88. data/lib/twterm.rb +3 -9
  89. data/spec/fixtures/status.json +107 -0
  90. data/spec/fixtures/user.json +102 -0
  91. data/spec/spec_helper.rb +7 -0
  92. data/spec/supports/shared_examples/abstract_key_mapper.rb +17 -0
  93. data/spec/twterm/extension/enumerator/lazy_spec.rb +11 -0
  94. data/spec/twterm/friendship_spec.rb +0 -102
  95. data/spec/twterm/image/blank_line_spec.rb +11 -0
  96. data/spec/twterm/image/brackets_spec.rb +12 -0
  97. data/spec/twterm/image/color_spec.rb +22 -0
  98. data/spec/twterm/image/empry_spec.rb +11 -0
  99. data/spec/twterm/image/horizontal_sequential_image_spec.rb +15 -0
  100. data/spec/twterm/image/parens_spec.rb +12 -0
  101. data/spec/twterm/image/string_image_spec.rb +12 -0
  102. data/spec/twterm/image/vertical_sequential_image_spec.rb +14 -0
  103. data/spec/twterm/image_spec.rb +65 -0
  104. data/spec/twterm/key_mapper/abstract_key_mapper_spec.rb +21 -0
  105. data/spec/twterm/key_mapper/app_key_mapper_spec.rb +7 -0
  106. data/spec/twterm/key_mapper/status_key_mapper_spec.rb +7 -0
  107. data/spec/twterm/key_mapper/tab_key_mapper_spec.rb +7 -0
  108. data/spec/twterm/repository/friendship_repository_spec.rb +108 -0
  109. data/spec/twterm/status_spec.rb +58 -0
  110. data/spec/twterm/user_spec.rb +94 -0
  111. data/twterm.gemspec +13 -10
  112. metadata +129 -35
  113. data/lib/twterm/event/notification.rb +0 -33
  114. data/lib/twterm/extensions/integer.rb +0 -5
  115. data/lib/twterm/filter_query_window.rb +0 -91
  116. data/lib/twterm/filterable_list.rb +0 -41
  117. data/lib/twterm/history/base.rb +0 -21
  118. data/lib/twterm/history/hashtag.rb +0 -13
  119. data/lib/twterm/history/savable.rb +0 -37
  120. data/lib/twterm/history/screen_name.rb +0 -11
  121. data/lib/twterm/promise.rb +0 -143
@@ -0,0 +1,109 @@
1
+ require 'concurrent'
2
+
3
+ require 'twterm/event/notification/success'
4
+ require 'twterm/image'
5
+ require 'twterm/publisher'
6
+ require 'twterm/tab/dumpable'
7
+ require 'twterm/tab/loadable'
8
+ require 'twterm/tab/searchable'
9
+
10
+ module Twterm
11
+ module Tab
12
+ class UserListManagement < Base
13
+ include Dumpable
14
+ include Loadable
15
+ include Publisher
16
+ include Searchable
17
+
18
+ @@lists = Concurrent::Array.new
19
+
20
+ def initialize(app, client, user_id)
21
+ super(app, client)
22
+
23
+ @user_id = user_id
24
+ @list_ids = Concurrent::Array.new
25
+
26
+ client.owned_lists.then do |lists|
27
+ @@lists = lists.sort_by(&:full_name)
28
+ render
29
+ end
30
+
31
+ client.memberships(user_id, filter_to_owned_lists: true, count: 1000).then do |lists|
32
+ @list_ids = lists.map(&:id)
33
+ initially_loaded!
34
+ end
35
+ end
36
+
37
+ def drawable_item_count
38
+ window.maxy / 3
39
+ end
40
+
41
+ def dump
42
+ user_id
43
+ end
44
+
45
+ def image
46
+ return Image.string(initially_loaded? ? 'No lists found' : 'Loading...') if items.empty?
47
+
48
+ drawable_items.map.with_index do |list, i|
49
+ cursor = Image.cursor(2, scroller.current_index?(i))
50
+
51
+ summary = Image.checkbox(@list_ids.include?(list.id)) - Image.whitespace - Image.string(list.full_name)
52
+ description = Image.string(' ') - Image.string(list.description)
53
+
54
+ cursor - Image.whitespace - (summary | description)
55
+ end
56
+ .intersperse(Image.blank_line)
57
+ .reduce(Image.empty, :|)
58
+ end
59
+
60
+ def items
61
+ @@lists
62
+ end
63
+
64
+ def matches?(list, query)
65
+ [
66
+ list.description,
67
+ list.full_name,
68
+ ].any? { |x| x.downcase.include?(query.downcase) }
69
+ end
70
+
71
+ def respond_to_key(key)
72
+ return true if scroller.respond_to_key(key)
73
+
74
+ case key
75
+ when 10 then toggle
76
+ end
77
+ end
78
+
79
+ def title
80
+ (user.nil? ? 'Loading' : "@#{user.screen_name} lists").freeze
81
+ end
82
+
83
+ private
84
+
85
+ attr_reader :list_ids, :user_id
86
+
87
+ def user
88
+ app.user_repository.find(user_id)
89
+ end
90
+
91
+ def toggle
92
+ list = scroller.current_item
93
+
94
+ if list_ids.include?(list.id)
95
+ client.remove_list_member(list.id, user_id).then do
96
+ @list_ids.delete(list.id)
97
+ publish(Event::Notification::Success.new("Removed @#{user.screen_name} from #{list.name}"))
98
+ end
99
+ else
100
+ client.add_list_member(list.id, user_id).then do
101
+ @list_ids.push(list.id)
102
+ publish(Event::Notification::Success.new("Added @#{user.screen_name} to #{list.name}"))
103
+ end
104
+ end
105
+ .then { render }
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,6 +1,7 @@
1
1
  require 'twterm/event/open_uri'
2
2
  require 'twterm/publisher'
3
3
  require 'twterm/tab/base'
4
+ require 'twterm/tab/user_list_management'
4
5
 
5
6
  module Twterm
6
7
  module Tab
@@ -24,19 +25,19 @@ module Twterm
24
25
  end
25
26
 
26
27
  def fetch
27
- update
28
+ render
28
29
  end
29
30
 
30
- def initialize(user_id)
31
- super()
31
+ def initialize(app, client, user_id)
32
+ super(app, client)
32
33
 
33
34
  self.title = 'Loading...'.freeze
34
35
  @user_id = user_id
35
36
 
36
- User.find_or_fetch(user_id).then do |user|
37
- refresh
37
+ find_or_fetch_user(user_id).then do |user|
38
+ render
38
39
 
39
- Client.current.lookup_friendships.then { render } unless Friendship.already_looked_up?(user_id)
40
+ client.lookup_friendships.then { render } unless app.friendship_repository.already_looked_up?(user_id)
40
41
  self.title = "@#{user.screen_name}"
41
42
  end
42
43
  end
@@ -47,6 +48,7 @@ module Twterm
47
48
  show_friends
48
49
  show_followers
49
50
  show_likes
51
+ manage_lists
50
52
  )
51
53
  items << :compose_direct_message unless myself?
52
54
  items << :open_website unless user.website.nil?
@@ -60,17 +62,11 @@ module Twterm
60
62
  def respond_to_key(key)
61
63
  return true if scroller.respond_to_key(key)
62
64
 
65
+ k = KeyMapper.instance
66
+
63
67
  case key
64
- when ?D
65
- compose_direct_message unless myself?
66
- when ?F
67
- follow unless myself?
68
68
  when 10
69
69
  perform_selected_action
70
- when ?t
71
- open_timeline_tab
72
- when ?W
73
- open_website
74
70
  else
75
71
  return false
76
72
  end
@@ -85,25 +81,25 @@ module Twterm
85
81
  end
86
82
 
87
83
  def block
88
- Client.current.block(user_id).then do |users|
89
- refresh
84
+ client.block(user_id).then do |users|
85
+ render
90
86
 
91
87
  user = users.first
92
- publish(Event::Notification.new(:message, 'Blocked @%s' % user.screen_name))
88
+ publish(Event::Notification::Success.new('Blocked @%s' % user.screen_name))
93
89
  end
94
90
  end
95
91
 
96
92
  def blocking?
97
- user.blocked_by?(Client.current.user_id)
93
+ app.friendship_repository.blocking?(client.user_id, user_id)
98
94
  end
99
95
 
100
96
  def compose_direct_message
101
- DirectMessageComposer.instance.compose(user)
97
+ app.direct_message_composer.compose(user)
102
98
  end
103
99
 
104
100
  def follow
105
- Client.current.follow(user_id).then do |users|
106
- refresh
101
+ client.follow(user_id).then do |users|
102
+ render
107
103
 
108
104
  user = users.first
109
105
  if user.protected?
@@ -111,42 +107,47 @@ module Twterm
111
107
  else
112
108
  msg = "Followed @#{user.screen_name}"
113
109
  end
114
- publish(Event::Notification.new(:message, msg))
110
+ publish(Event::Notification::Success.new(msg))
115
111
  end
116
112
  end
117
113
 
118
114
  def followed?
119
- user.following?(Client.current.user_id)
115
+ app.friendship_repository.following?(user_id, client.user_id)
120
116
  end
121
117
 
122
118
  def following?
123
- user.followed_by?(Client.current.user_id)
119
+ app.friendship_repository.following?(client.user_id, user_id)
124
120
  end
125
121
 
126
122
  def following_requested?
127
- user.following_requested_by?(Client.current.user_id)
123
+ app.friendship_repository.following_requested?(client.user_id, user_id)
128
124
  end
129
125
 
130
126
  def mute
131
- Client.current.mute(user_id).then do |users|
132
- refresh
127
+ client.mute(user_id).then do |users|
128
+ render
133
129
 
134
130
  user = users.first
135
- publish(Event::Notification.new(:message, 'Muted @%s' % user.screen_name))
131
+ publish(Event::Notification::Success.new('Muted @%s' % user.screen_name))
136
132
  end
137
133
  end
138
134
 
139
135
  def muting?
140
- user.muted_by?(Client.current.user_id)
136
+ app.friendship_repository.muting?(client.user_id, user_id)
141
137
  end
142
138
 
143
139
  def myself?
144
- user_id == Client.current.user_id
140
+ user_id == client.user_id
141
+ end
142
+
143
+ def open_list_management_tab
144
+ tab = Tab::UserListManagement.new(app, client, user_id)
145
+ app.tab_manager.add_and_show(tab)
145
146
  end
146
147
 
147
148
  def open_timeline_tab
148
- tab = Tab::Statuses::UserTimeline.new(user_id)
149
- TabManager.instance.add_and_show(tab)
149
+ tab = Tab::Statuses::UserTimeline.new(app, client, user_id)
150
+ app.tab_manager.add_and_show(tab)
150
151
  end
151
152
 
152
153
  def open_website
@@ -159,6 +160,8 @@ module Twterm
159
160
  case scroller.current_item
160
161
  when :compose_direct_message
161
162
  compose_direct_message
163
+ when :manage_lists
164
+ open_list_management_tab
162
165
  when :open_timeline_tab
163
166
  open_timeline_tab
164
167
  when :open_website
@@ -185,138 +188,134 @@ module Twterm
185
188
  end
186
189
 
187
190
  def show_likes
188
- tab = Tab::Statuses::Favorites.new(user_id)
189
- TabManager.instance.add_and_show(tab)
191
+ tab = Tab::Statuses::Favorites.new(app, client, user_id)
192
+ app.tab_manager.add_and_show(tab)
190
193
  end
191
194
 
192
195
  def show_followers
193
- tab = Tab::Users::Followers.new(user_id)
194
- TabManager.instance.add_and_show(tab)
196
+ tab = Tab::Users::Followers.new(app, client, user_id)
197
+ app.tab_manager.add_and_show(tab)
195
198
  end
196
199
 
197
200
  def show_friends
198
- tab = Tab::Users::Friends.new(user_id)
199
- TabManager.instance.add_and_show(tab)
201
+ tab = Tab::Users::Friends.new(app, client, user_id)
202
+ app.tab_manager.add_and_show(tab)
200
203
  end
201
204
 
202
205
  def unblock
203
- Client.current.unblock(user_id).then do |users|
204
- refresh
206
+ client.unblock(user_id).then do |users|
207
+ render
205
208
 
206
209
  user = users.first
207
- publish(Event::Notification.new(:message, 'Unblocked @%s' % user.screen_name))
210
+ publish(Event::Notification::Success.new('Unblocked @%s' % user.screen_name))
208
211
  end
209
212
  end
210
213
 
211
214
  def unfollow
212
- Client.current.unfollow(user_id).then do |users|
213
- refresh
215
+ client.unfollow(user_id).then do |users|
216
+ render
214
217
 
215
218
  user = users.first
216
- publish(Event::Notification.new(:message, 'Unfollowed @%s' % user.screen_name))
219
+ publish(Event::Notification::Success.new('Unfollowed @%s' % user.screen_name))
217
220
  end
218
221
  end
219
222
 
220
223
  def unmute
221
- Client.current.unmute(user_id).then do |users|
222
- refresh
224
+ client.unmute(user_id).then do |users|
225
+ render
223
226
 
224
227
  user = users.first
225
- publish(Event::Notification.new(:message, 'Unmuted @%s' % user.screen_name))
228
+ publish(Event::Notification::Success.new('Unmuted @%s' % user.screen_name))
226
229
  end
227
230
  end
228
231
 
229
- def update
232
+ def image
230
233
  if user.nil?
231
- User.find_or_fetch(user_id).then { update }
232
- return
233
- end
234
-
235
- window.setpos(2, 3)
236
- window.bold { window.addstr(user.name) }
237
- window.addstr(" (@#{user.screen_name})")
238
-
239
- window.with_color(:yellow) { window.addstr(' [protected]') } if user.protected?
240
- window.with_color(:cyan) { window.addstr(' [verified]') } if user.verified?
241
-
242
- window.setpos(5, 4)
243
- if myself?
244
- window.with_color(:yellow) { window.addstr(' [your account]') }
245
- else
246
- window.with_color(:green) { window.addstr(' [following]') } if following?
247
- window.with_color(:white) { window.addstr(' [not following]') } if !following? && !blocking? && !following_requested?
248
- window.with_color(:yellow) { window.addstr(' [following requested]') } if following_requested?
249
- window.with_color(:cyan) { window.addstr(' [follows you]') } if followed?
250
- window.with_color(:red) { window.addstr(' [muting]') } if muting?
251
- window.with_color(:red) { window.addstr(' [blocking]') } if blocking?
252
- end
253
-
254
- user.description.split_by_width(window.maxx - 6).each.with_index(7) do |line, i|
255
- window.setpos(i, 5)
256
- window.addstr(line)
234
+ find_or_fetch_user(user_id).then { render }
235
+ return Image.empty
257
236
  end
258
237
 
259
- window.setpos(8 + bio_height, 5)
260
- window.addstr("Location: #{user.location}") unless user.location.nil?
238
+ name = !Image.string(user.name) - Image.whitespace - Image.string("@#{user.screen_name}").parens
261
239
 
262
- current_line = 11 + bio_height
240
+ badges = [
241
+ (Image.string('protected').brackets.color(:yellow) if user.protected?),
242
+ (Image.string('verified').brackets.color(:cyan) if user.verified?),
243
+ ].compact.intersperse(Image.whitespace).reduce(Image.empty, :-)
263
244
 
264
- drawable_items.each.with_index(0) do |item, i|
265
- if scroller.current_item? i
266
- window.setpos(current_line, 3)
267
- window.with_color(:black, :magenta) { window.addch(' ') }
245
+ status =
246
+ if myself?
247
+ Image.string('your account').brackets.color(:yellow)
248
+ else
249
+ [
250
+ ['following', :green, following?],
251
+ ['not following', :white, !following? && !blocking? && !following_requested?],
252
+ ['following requested', :yellow, following_requested?],
253
+ ['follows you', :cyan, followed?],
254
+ ['muting', :red, muting?],
255
+ ['blocking', :red, blocking?],
256
+ ]
257
+ .select { |_, _, p| p }
258
+ .map { |s, c, _| Image.string(s).brackets.color(c) }
259
+ .intersperse(Image.whitespace)
260
+ .reduce(Image.empty, :-)
268
261
  end
269
262
 
270
- window.setpos(current_line, 5)
271
- case item
272
- when :compose_direct_message
273
- window.addstr('[ ] Compose direct message')
274
- window.setpos(current_line, 6)
275
- window.bold { window.addch(?D) }
276
- when :toggle_block
277
- if blocking?
278
- window.addstr(' Unblock this user')
279
- else
280
- window.addstr(' Block this user')
281
- end
282
- when :toggle_follow
283
- if following?
284
- window.addstr(' Unfollow this user')
285
- elsif following_requested?
286
- window.addstr(' Following request sent')
287
- else
288
- window.addstr('[ ] Follow this user')
289
- window.setpos(current_line, 6)
290
- window.bold { window.addch(?F) }
263
+ description = user.description.split_by_width(window.maxx - 4)
264
+ .map(&Image.method(:string))
265
+ .reduce(Image.empty, :|)
266
+
267
+ location = Image.string("Location: #{user.location}")
268
+
269
+ foo = drawable_items.map.with_index(0) do |item, i|
270
+ Image.cursor(1, scroller.current_index?(i)) - Image.whitespace -
271
+ case item
272
+ when :compose_direct_message
273
+ Image.string('Compose direct message')
274
+ when :toggle_block
275
+ if blocking?
276
+ Image.string('Unblock this user')
277
+ else
278
+ Image.string('Block this user')
279
+ end
280
+ when :toggle_follow
281
+ if following?
282
+ Image.string('Unfollow this user')
283
+ elsif following_requested?
284
+ Image.string('Following request sent')
285
+ else
286
+ Image.string('Follow this user')
287
+ end
288
+ when :toggle_mute
289
+ if muting?
290
+ Image.string('Unmute this user')
291
+ else
292
+ Image.string('Mute this user')
293
+ end
294
+ when :open_timeline_tab
295
+ Image.number(user.statuses_count) - Image.whitespace - Image.plural(user.statuses_count, 'tweet')
296
+ when :open_website
297
+ Image.string("Open website (#{user.website})")
298
+ when :show_likes
299
+ Image.number(user.favorites_count) - Image.whitespace - Image.plural(user.favorites_count, 'like')
300
+ when :show_followers
301
+ Image.number(user.followers_count) - Image.whitespace - Image.plural(user.followers_count, 'follower')
302
+ when :show_friends
303
+ Image.number(user.friends_count) - Image.whitespace - Image.string('following')
304
+ when :manage_lists
305
+ Image.string('Add to / Remove from lists')
291
306
  end
292
- when :toggle_mute
293
- if muting?
294
- window.addstr(' Unmute this user')
295
- else
296
- window.addstr(' Mute this user')
297
- end
298
- when :open_timeline_tab
299
- window.addstr("[ ] #{user.statuses_count.format} tweets")
300
- window.setpos(current_line, 6)
301
- window.bold { window.addch(?t) }
302
- when :open_website
303
- window.addstr("[ ] Open website (#{user.website})")
304
- window.setpos(current_line, 6)
305
- window.bold { window.addch(?W) }
306
- when :show_likes
307
- window.addstr(" #{user.favorites_count.format} likes")
308
- when :show_followers
309
- window.addstr(" #{user.followers_count.format} followers")
310
- when :show_friends
311
- window.addstr(" #{user.friends_count.format} following")
312
- end
313
-
314
- current_line += 2
315
307
  end
308
+ .intersperse(Image.blank_line)
309
+ .reduce(Image.empty, :|)
310
+
311
+ [name, badges, status, description, location, foo]
312
+ .compact
313
+ .intersperse(Image.blank_line)
314
+ .reduce(Image.empty, :|)
316
315
  end
317
316
 
318
317
  def user
319
- User.find(user_id)
318
+ app.user_repository.find(user_id)
320
319
  end
321
320
  end
322
321
  end
@@ -1,11 +1,14 @@
1
+ require 'concurrent'
2
+
1
3
  require 'twterm/tab/base'
4
+ require 'twterm/tab/loadable'
2
5
 
3
6
  module Twterm
4
7
  module Tab
5
8
  module Users
6
9
  class Base < Tab::Base
7
- include FilterableList
8
- include Scrollable
10
+ include Loadable
11
+ include Searchable
9
12
 
10
13
  attr_reader :user_ids
11
14
 
@@ -13,37 +16,35 @@ module Twterm
13
16
  (window.maxy - 6).div(3)
14
17
  end
15
18
 
16
- def close
17
- @instance_keeper.kill
18
- super
19
- end
20
-
21
19
  def fetch; end
22
20
 
23
- def initialize
24
- super()
25
- @user_ids = []
26
-
27
- @instance_keeper = Scheduler.new(300) { items.each(&:touch!) }
21
+ def initialize(app, client)
22
+ super(app, client)
23
+ @user_ids = Concurrent::Array.new
28
24
  end
29
25
 
30
26
  def items
31
- users = user_ids.map { |id| User.find(id) }.reject(&:nil?)
32
- filter_query.empty? ? users : users.select { |user| user.matches?(filter_query) }
27
+ user_ids.map { |id| app.user_repository.find(id) }.compact
28
+ end
29
+
30
+ def matches?(user, query)
31
+ [
32
+ user.name,
33
+ user.screen_name,
34
+ user.description
35
+ ].compact.any? { |x| x.downcase.include?(query.downcase) }
33
36
  end
34
37
 
35
38
  def respond_to_key(key)
36
39
  return true if scroller.respond_to_key(key)
37
40
 
41
+ k = KeyMapper.instance
42
+
38
43
  case key
39
- when 10, ?U
44
+ when 10
40
45
  show_user
41
- when 18
46
+ when k[:tab, :reload]
42
47
  fetch
43
- when ?q
44
- reset_filter
45
- when ?/
46
- filter
47
48
  else
48
49
  return false
49
50
  end
@@ -59,31 +60,28 @@ module Twterm
59
60
 
60
61
  def show_user
61
62
  user = current_item
62
- tab = Tab::UserTab.new(user.id)
63
- TabManager.instance.add_and_show(tab)
63
+ tab = Tab::UserTab.new(app, client, user.id)
64
+ app.tab_manager.add_and_show(tab)
64
65
  end
65
66
 
66
- def update
67
- window.setpos(2, 3)
68
- window.bold { window.addstr(title) }
69
-
70
- drawable_items.each.with_index(0) do |user, i|
71
- window.with_color(:black, :magenta) do
72
- window.setpos(i * 3 + 5, 3)
73
- window.addch(' ')
74
- window.setpos(i * 3 + 6, 3)
75
- window.addch(' ')
76
- end if scroller.current_item?(i)
77
-
78
- window.setpos(i * 3 + 5, 5)
79
- window.bold { window.with_color(user.color) { window.addstr(user.name) } }
80
- window.addstr(" (@#{user.screen_name})")
81
- window.with_color(:yellow) { window.addstr(' [protected]') } if user.protected?
82
- window.with_color(:cyan) { window.addstr(' [verified]') } if user.verified?
83
- window.setpos(i * 3 + 6, 7)
67
+ def image
68
+ return Image.string(initially_loaded? ? 'No result found' : 'Loading...') if items.empty?
69
+
70
+ drawable_items.map.with_index(0) do |user, i|
71
+ cursor = Image.cursor(2, scroller.current_index?(i))
72
+
73
+ header = [
74
+ !Image.string(user.name).color(user.color),
75
+ Image.string("@#{user.screen_name}"),
76
+ (Image.string('protected').brackets if user.protected?),
77
+ (Image.string('verified').brackets if user.verified?),
78
+ ].compact.intersperse(Image.whitespace).reduce(Image.empty, :-)
79
+
84
80
  bio_chunks = user.description.gsub(/[\n\r]/, ' ').split_by_width(window.maxx - 10)
85
- window.addstr(bio_chunks[0] + (bio_chunks[1].nil? ? '' : '...'))
81
+ cursor - Image.whitespace - (header | Image.string("#{bio_chunks[0]}#{'...' unless bio_chunks[1].nil?}"))
86
82
  end
83
+ .intersperse(Image.blank_line)
84
+ .reduce(Image.empty, :|)
87
85
  end
88
86
  end
89
87
  end
@@ -13,18 +13,21 @@ module Twterm
13
13
  end
14
14
 
15
15
  def fetch
16
- Client.current.followers(user_id) do |users|
16
+ client.followers(user_id) do |users|
17
17
  @user_ids.concat(users.map(&:id)).uniq!
18
- refresh
18
+ render
19
19
  end
20
20
  end
21
21
 
22
- def initialize(user_id)
23
- super()
22
+ def initialize(app, client, user_id)
23
+ super(app, client)
24
24
 
25
25
  @user_id = user_id
26
26
 
27
- fetch { move_to_top }
27
+ fetch.then do
28
+ initially_loaded!
29
+ move_to_top
30
+ end
28
31
  end
29
32
 
30
33
  def title
@@ -34,7 +37,7 @@ module Twterm
34
37
  private
35
38
 
36
39
  def user
37
- User.find(user_id)
40
+ app.user_repository.find(user_id)
38
41
  end
39
42
  end
40
43
  end