twterm 1.3.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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