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
data/lib/twterm/status.rb CHANGED
@@ -1,15 +1,13 @@
1
+ require 'concurrent'
2
+
1
3
  module Twterm
2
4
  class Status
3
- MAX_CACHED_TIME = 3600
4
-
5
- attr_reader :appeared_at, :created_at, :favorite_count, :favorited, :id,
5
+ attr_reader :created_at, :favorite_count, :favorited, :id,
6
6
  :in_reply_to_status_id, :media, :retweet_count, :retweeted,
7
- :retweeted_by_user_id, :text, :touched_at, :urls, :user_id
7
+ :retweeted_status_id, :text, :urls, :user_id
8
8
  alias_method :favorited?, :favorited
9
9
  alias_method :retweeted?, :retweeted
10
10
 
11
- @@instances = {}
12
-
13
11
  def ==(other)
14
12
  other.is_a?(self.class) && id == other.id
15
13
  end
@@ -25,150 +23,58 @@ module Twterm
25
23
  end
26
24
 
27
25
  def favorite!
28
- @favorite_count += 1
29
26
  @favorited = true
30
27
  end
31
28
 
32
- def matches?(query)
33
- [text, user.screen_name, user.name]
34
- .any? { |x| x.downcase.include?(query.downcase) }
35
- end
36
-
37
- def in_reply_to_status(&block)
38
- Promise.new do |resolve, reject|
39
- (resolve.(nil) && next) if in_reply_to_status_id.nil?
40
-
41
- instance = Status.find(in_reply_to_status_id)
42
- (resolve.(instance) && next) if instance
43
-
44
- Client.current.show_status(in_reply_to_status_id)
45
- .then do |status|
46
- resolve.(status)
47
- end
48
- end
49
- end
50
-
51
- def initialize(tweet)
29
+ def initialize(tweet, is_retweeted_status = false)
52
30
  unless tweet.retweeted_status.is_a? Twitter::NullObject
53
- @retweeted_by_user_id = tweet.user.id
54
- User.new(tweet.user)
55
- retweeted_at = tweet.created_at.dup.localtime
56
- tweet = tweet.retweeted_status
31
+ @retweeted_status_id = tweet.retweeted_status.id
57
32
  end
58
33
 
59
34
  @id = tweet.id
60
35
  @text = CGI.unescapeHTML(tweet.full_text.dup)
61
36
  @created_at = tweet.created_at.dup.localtime
62
- @appeared_at = retweeted_at || @created_at
63
- @retweet_count = tweet.retweet_count
64
- @favorite_count = tweet.favorite_count
65
37
  @in_reply_to_status_id = tweet.in_reply_to_status_id
66
38
 
67
- @retweeted = tweet.retweeted?
68
- @favorited = tweet.favorited?
39
+ update!(tweet, is_retweeted_status)
69
40
 
70
41
  @media = tweet.media
71
42
  @urls = tweet.urls
72
43
 
73
44
  @user_id = tweet.user.id
74
- User.new(tweet.user)
75
45
 
76
46
  @splitted_text = {}
77
47
 
78
48
  expand_url!
79
-
80
- @touched_at = Time.now
81
-
82
- tweet.hashtags.each do |hashtag|
83
- History::Hashtag.instance.add(hashtag.text)
84
- end
85
-
86
- @@instances[id] = self
87
49
  end
88
50
 
89
- def replies
90
- Status.all.select { |s| s.in_reply_to_status_id == id }
51
+ def retweet?
52
+ !retweeted_status_id.nil?
91
53
  end
92
54
 
93
55
  def retweet!
94
- @retweet_count += 1
95
56
  @retweeted = true
96
57
  end
97
58
 
98
- def retweeted_by
99
- User.find(@retweeted_by_user_id)
100
- end
101
-
102
59
  def split(width)
103
60
  @splitted_text[width] ||= @text.split_by_width(width)
104
61
  end
105
62
 
106
- def touch!
107
- @touched_at = Time.now
108
- end
109
-
110
63
  def unfavorite!
111
- @favorite_count -= 1
112
64
  @favorited = false
113
65
  end
114
66
 
115
- def update!(tweet)
116
- return self if recently_updated?
67
+ def unretweet!
68
+ @retweeted = false
69
+ end
117
70
 
71
+ def update!(tweet, is_retweeted_status = false)
118
72
  @retweet_count = tweet.retweet_count
119
73
  @favorite_count = tweet.favorite_count
120
- @retweeted = tweet.retweeted?
74
+ @retweeted = tweet.retweeted? unless is_retweeted_status
121
75
  @favorited = tweet.favorited?
122
76
 
123
- @updated_at = Time.now
124
-
125
77
  self
126
78
  end
127
-
128
- def user
129
- User.find(user_id)
130
- end
131
-
132
- def self.all
133
- @@instances.values
134
- end
135
-
136
- def self.cleanup
137
- TabManager.instance.each_tab do |tab|
138
- tab.touch_statuses if tab.is_a?(Tab::Statuses::Base)
139
- end
140
- cond = -> (status) { status.touched_at > Time.now - MAX_CACHED_TIME }
141
- statuses = all.select(&cond)
142
- status_ids = statuses.map(&:id)
143
- @@instances = Hash[status_ids.zip(statuses)]
144
- end
145
-
146
- def self.delete(id)
147
- @@instances.delete(id)
148
- end
149
-
150
- def self.find(id)
151
- @@instances[id]
152
- end
153
-
154
- def self.find_or_fetch(id)
155
- Promise.new do |resolve, reject|
156
- instance = find(id)
157
- (resolve.(instance) && next) if instance
158
-
159
- Client.current.show_status(id) { |status| resolve.(status) }
160
- end
161
- end
162
-
163
- def self.new(tweet)
164
- instance = find(tweet.id)
165
- instance.nil? ? super : instance.update!(tweet)
166
- end
167
-
168
- private
169
-
170
- def recently_updated?
171
- !@updated_at.nil? && @updated_at + 60 > Time.now
172
- end
173
79
  end
174
80
  end
@@ -1,6 +1,7 @@
1
1
  require 'twterm/event/favorite'
2
2
  require 'twterm/event/follow'
3
- require 'twterm/event/notification'
3
+ require 'twterm/event/notification/error'
4
+ require 'twterm/event/notification/info'
4
5
  require 'twterm/event/status/mention'
5
6
  require 'twterm/event/status/timeline'
6
7
  require 'twterm/publisher'
@@ -17,26 +18,26 @@ module Twterm
17
18
 
18
19
  @streaming_thread = Thread.new do
19
20
  begin
20
- publish(Event::Notification.new(:message, 'Trying to connect to Twitter...'))
21
+ publish(Event::Notification::Info.new('Trying to connect to Twitter...'))
21
22
  streaming_client.user do |event|
22
23
  keep_alive!
23
24
 
24
25
  case event
25
26
  when Twitter::Tweet
26
- status = Status.new(event)
27
+ status = status_repository.create(event)
27
28
  publish(Event::Status::Timeline.new(status))
28
29
  publish(Event::Status::Mention.new(status)) if status.text.include?('@%s' % screen_name)
29
30
  when Twitter::Streaming::Event
30
31
  case event.name
31
32
  when :favorite
32
- user = User.new(event.source)
33
- status = Status.new(event.target_object)
33
+ user = user_repository.create(event.source)
34
+ status = status_repository.create(event.target_object)
34
35
 
35
36
  event = Event::Favorite.new(user, status, self)
36
37
  publish(event)
37
38
  when :follow
38
- source = User.new(event.source)
39
- target = User.new(event.target)
39
+ source = user_repository.create(event.source)
40
+ target = user_repository.create(event.target)
40
41
 
41
42
  event = Event::Follow.new(source, target, self)
42
43
 
@@ -50,15 +51,15 @@ module Twterm
50
51
  end
51
52
  end
52
53
  rescue Twitter::Error::TooManyRequests
53
- publish(Event::Notification.new(:error, 'Rate limit exceeded'))
54
+ publish(Event::Notification::Error.new('Rate limit exceeded'))
54
55
  sleep 120
55
56
  retry
56
- rescue Errno::ENETUNREACH, Resolv::ResolvError
57
- publish(Event::Notification.new(:error, 'Network is unavailable'))
57
+ rescue Errno::ENETUNREACH, Errno::ETIMEDOUT, Resolv::ResolvError
58
+ publish(Event::Notification::Error.new('Network is unavailable'))
58
59
  sleep 30
59
60
  retry
60
61
  rescue Twitter::Error => e
61
- publish(Event::Notification.new(:error, e.message))
62
+ publish(Event::Notification::Error.new(e.message))
62
63
  end
63
64
  end
64
65
  end
@@ -89,7 +90,7 @@ module Twterm
89
90
  end
90
91
 
91
92
  def user_stream_connected!
92
- publish(Event::Notification.new(:message, 'Connection established')) unless user_stream_connected?
93
+ publish(Event::Notification::Info.new('Connection established')) unless user_stream_connected?
93
94
  @user_stream_connected = true
94
95
  end
95
96
 
@@ -1,4 +1,7 @@
1
+ require 'concurrent'
2
+
1
3
  require 'twterm/event/screen/resize'
4
+ require 'twterm/image'
2
5
  require 'twterm/subscriber'
3
6
 
4
7
  module Twterm
@@ -19,13 +22,45 @@ module Twterm
19
22
  window.close
20
23
  end
21
24
 
22
- def initialize
25
+ def find_or_fetch_status(id)
26
+ status = app.status_repository.find(id)
27
+
28
+ if status
29
+ Concurrent::Promise.fulfill(status)
30
+ else
31
+ client.show_status(id)
32
+ end
33
+ end
34
+
35
+ def find_or_fetch_list(id)
36
+ list = app.list_repository.find(id)
37
+
38
+ if list
39
+ Concurrent::Promise.fulfill(list)
40
+ else
41
+ client.list(id)
42
+ end
43
+ end
44
+
45
+ def find_or_fetch_user(id)
46
+ user = app.user_repository.find(id)
47
+
48
+ if user
49
+ Concurrent::Promise.fulfill(user)
50
+ else
51
+ client.show_user(id)
52
+ end
53
+ end
54
+
55
+ def initialize(app, client)
56
+ @app, @client = app, client
57
+
23
58
  @window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx, 3, 0)
24
59
 
25
60
  subscribe(Event::Screen::Resize, :resize)
26
61
  end
27
62
 
28
- def refresh
63
+ def render
29
64
  Thread.new do
30
65
  refresh_mutex.synchronize do
31
66
  window.clear
@@ -38,8 +73,7 @@ module Twterm
38
73
  end
39
74
  end
40
75
 
41
- update
42
- window.refresh
76
+ view.at(1, 2).render
43
77
  end if refreshable?
44
78
  end
45
79
  end
@@ -50,11 +84,17 @@ module Twterm
50
84
 
51
85
  def title=(title)
52
86
  @title = title
53
- TabManager.instance.refresh_window
87
+ app.tab_manager.refresh_window
54
88
  end
55
89
 
56
90
  private
57
91
 
92
+ attr_reader :app, :client
93
+
94
+ def image
95
+ Image.string('view method is not implemented')
96
+ end
97
+
58
98
  def refresh_mutex
59
99
  @refresh_mutex ||= Mutex.new
60
100
  end
@@ -63,7 +103,7 @@ module Twterm
63
103
  !(
64
104
  refresh_mutex.locked? ||
65
105
  closed? ||
66
- TabManager.instance.current_tab.object_id != object_id
106
+ app.tab_manager.current_tab.object_id != object_id
67
107
  )
68
108
  end
69
109
 
@@ -72,8 +112,8 @@ module Twterm
72
112
  window.move(3, 0)
73
113
  end
74
114
 
75
- def update
76
- fail NotImplementedError, 'update method must be implemented'
115
+ def view
116
+ View.new(window, image)
77
117
  end
78
118
  end
79
119
  end
@@ -2,49 +2,83 @@ require 'twterm/direct_message_composer'
2
2
  require 'twterm/event/direct_message/fetched'
3
3
  require 'twterm/subscriber'
4
4
  require 'twterm/tab/base'
5
+ require 'twterm/tab/loadable'
6
+ require 'twterm/tab/searchable'
5
7
 
6
8
  module Twterm
7
9
  module Tab
8
10
  module DirectMessage
9
11
  class Conversation < Base
10
- include FilterableList
11
- include Scrollable
12
+ include Searchable
12
13
  include Subscriber
14
+ include Loadable
13
15
 
14
16
  def drawable_item_count
15
17
  messages.drop(scroller.offset).lazy
16
18
  .map { |m| m.text.split_by_width(window.maxx - 4).count + 2 }
17
19
  .scan(0, :+)
18
- .select { |l| l < window.maxy }
20
+ .each_cons(2)
21
+ .select { |_, l| l < window.maxy }
19
22
  .count
20
23
  end
21
24
 
22
- def initialize(conversation)
23
- super()
25
+ def image
26
+ return Image.string(initially_loaded? ? 'No results found' : 'Loading...') if items.empty?
27
+
28
+ scroller.drawable_items.map.with_index(0) do |message, i|
29
+ sender = app.user_repository.find(message.sender_id)
30
+
31
+ header = [
32
+ !Image.string(sender.name).color(sender.color),
33
+ Image.string("@#{sender.screen_name}").parens,
34
+ Image.string(message.date.to_s).brackets,
35
+ ].intersperse(Image.whitespace).reduce(Image.empty, :-)
36
+
37
+ body = message.text.split_by_width(window.maxx - 4)
38
+ .map { |x| Image.string(x) }
39
+ .reduce(Image.empty, :|)
40
+
41
+ m = header | body
42
+
43
+ cursor = Image.cursor(m.height, scroller.current_index?(i))
44
+
45
+ cursor - Image.whitespace - m
46
+ end
47
+ .intersperse(Image.blank_line)
48
+ .reduce(Image.empty, :|)
49
+ end
50
+
51
+ def initialize(app, client, conversation)
52
+ super(app, client)
24
53
 
25
54
  @conversation = conversation
26
55
 
27
- subscribe(Event::DirectMessage::Fetched) { refresh }
56
+ subscribe(Event::DirectMessage::Fetched) { initially_loaded! }
28
57
  end
29
58
 
30
59
  def items
31
- if filter_query.empty?
32
- messages
33
- else
34
- messages.select { |m| m.matches?(filter_query) }
35
- end
60
+ messages
61
+ end
62
+
63
+ def matches?(message, query)
64
+ sender = app.user_repository.find(message.sender_id)
65
+
66
+ [
67
+ message.text,
68
+ sender.screen_name,
69
+ sender.name
70
+ ].any? { |x| x.downcase.include?(query.downcase) }
36
71
  end
37
72
 
38
73
  def respond_to_key(key)
39
74
  return true if scroller.respond_to_key(key)
40
75
 
76
+ k = KeyMapper.instance
77
+
41
78
  case key
42
- when ?/
43
- filter
44
- when ?n, ?r
45
- DirectMessageComposer.instance.compose(conversation.collocutor)
46
- when ?q
47
- reset_filter
79
+ when k[:status, :compose], k[:status, :reply]
80
+ collocutor = app.user_repository.find(conversation.collocutor_id)
81
+ app.direct_message_composer.compose(collocutor)
48
82
  else
49
83
  return false
50
84
  end
@@ -52,42 +86,9 @@ module Twterm
52
86
  true
53
87
  end
54
88
 
55
- def update
56
- line = 0
57
-
58
- scroller.drawable_items.each.with_index(0) do |message, i|
59
- formatted_lines = message.text.split_by_width(window.maxx - 4).count
60
-
61
- window.with_color(:black, :magenta) do
62
- formatted_lines.+(1).times do |j|
63
- window.setpos(line + j, 0)
64
- window.addch(' ')
65
- end
66
- end if scroller.current_item?(i)
67
-
68
- window.setpos(line, 2)
69
-
70
- window.bold do
71
- window.with_color(message.sender.color) do
72
- window.addstr(message.sender.name)
73
- end
74
- end
75
-
76
- window.addstr(' (@%s)' % message.sender.screen_name)
77
- window.addstr(' [%s]' % message.date)
78
-
79
- message.text.split_by_width(window.maxx - 4).each do |str|
80
- line += 1
81
- window.setpos(line, 2)
82
- window.addstr(str)
83
- end
84
-
85
- line += 2
86
- end
87
- end
88
-
89
89
  def title
90
- '@%s messages' % conversation.collocutor.screen_name
90
+ collocutor = app.user_repository.find(conversation.collocutor_id)
91
+ '@%s messages' % collocutor.screen_name
91
92
  end
92
93
 
93
94
  private
@@ -2,24 +2,47 @@ require 'twterm/direct_message_composer'
2
2
  require 'twterm/event/direct_message/fetched'
3
3
  require 'twterm/subscriber'
4
4
  require 'twterm/tab/base'
5
+ require 'twterm/tab/loadable'
5
6
  require 'twterm/tab/direct_message/conversation'
6
7
 
7
8
  module Twterm
8
9
  module Tab
9
10
  module DirectMessage
10
11
  class ConversationList < Base
11
- include FilterableList
12
- include Scrollable
12
+ include Loadable
13
+ include Searchable
13
14
  include Subscriber
14
15
 
15
16
  def drawable_item_count
16
17
  window.maxy.-(2).div(3)
17
18
  end
18
19
 
19
- def initialize
20
- super
20
+ def image
21
+ return Image.string(initially_loaded? ? 'No results found' : 'Loading...') if items.empty?
21
22
 
22
- subscribe(Event::DirectMessage::Fetched) { refresh }
23
+ scroller.drawable_items.map.with_index(0) do |conversation, i|
24
+ cursor = Image.cursor(2, scroller.current_index?(i))
25
+
26
+ collocutor = app.user_repository.find(conversation.collocutor_id)
27
+
28
+ header = [
29
+ !Image.string(collocutor.name).color(collocutor.color),
30
+ Image.string("@#{collocutor.screen_name}").parens,
31
+ Image.string(conversation.updated_at.to_s).brackets,
32
+ ].intersperse(Image.whitespace).reduce(Image.empty, :-)
33
+
34
+ body = Image.string(conversation.preview.split_by_width(window.maxx - 4).first)
35
+
36
+ cursor - Image.whitespace - (header | body)
37
+ end
38
+ .intersperse(Image.blank_line)
39
+ .reduce(Image.empty, :|)
40
+ end
41
+
42
+ def initialize(app, client)
43
+ super(app, client)
44
+
45
+ subscribe(Event::DirectMessage::Fetched) { initially_loaded! }
23
46
  end
24
47
 
25
48
  def ==(other)
@@ -27,26 +50,31 @@ module Twterm
27
50
  end
28
51
 
29
52
  def items
30
- if filter_query.empty?
31
- Client.current.direct_message_conversations
32
- else
33
- Client.current.direct_message_conversations.select { |c| c.matches?(filter_query) }
34
- end
53
+ client.direct_message_conversations
54
+ end
55
+
56
+ def matches?(conversation, query)
57
+ collocutor = app.user_repository.find(conversation.collocutor_id)
58
+
59
+ [
60
+ collocutor.name,
61
+ collocutor.screen_name,
62
+ conversation.preview
63
+ ].any? { |x| x.downcase.include?(query.downcase) }
35
64
  end
36
65
 
37
66
  def respond_to_key(key)
38
67
  return true if scroller.respond_to_key(key)
39
68
 
69
+ k = KeyMapper.instance
70
+
40
71
  case key
41
72
  when 10
42
73
  open_conversation
43
- when ?n, ?r
74
+ when k[:status, :compose], k[:status, :reply]
44
75
  conversation = current_item
45
- DirectMessageComposer.instance.compose(conversation.collocutor)
46
- when ?/
47
- filter
48
- when ?q
49
- reset_filter
76
+ collocutor = app.user_repository.find(conversation.collocutor_id)
77
+ app.direct_message_composer.compose(collocutor)
50
78
  else
51
79
  return false
52
80
  end
@@ -54,33 +82,6 @@ module Twterm
54
82
  true
55
83
  end
56
84
 
57
- def update
58
- scroller.drawable_items.each.with_index(0) do |conversation, i|
59
- line = 3 * i
60
-
61
- window.with_color(:black, :magenta) do
62
- 2.times do |j|
63
- window.setpos(line + j, 0)
64
- window.addch(' ')
65
- end
66
- end if scroller.current_item?(i)
67
-
68
- window.setpos(line, 2)
69
-
70
- window.bold do
71
- window.with_color(conversation.collocutor.color) do
72
- window.addstr(conversation.collocutor.name)
73
- end
74
- end
75
-
76
- window.addstr(' (@%s)' % conversation.collocutor.screen_name)
77
- window.addstr(' [%s]' % conversation.updated_at)
78
-
79
- window.setpos(line + 1, 2)
80
- window.addstr(conversation.preview.split_by_width(window.maxx - 4).first)
81
- end
82
- end
83
-
84
85
  def title
85
86
  'Direct Messages'
86
87
  end
@@ -90,8 +91,8 @@ module Twterm
90
91
  def open_conversation
91
92
  conversation = scroller.current_item
92
93
 
93
- tab = Tab::DirectMessage::Conversation.new(conversation)
94
- TabManager.instance.add_and_show(tab)
94
+ tab = Tab::DirectMessage::Conversation.new(app, client, conversation)
95
+ app.tab_manager.add_and_show(tab)
95
96
  end
96
97
  end
97
98
  end
@@ -2,7 +2,7 @@ module Twterm
2
2
  module Tab
3
3
  module Dumpable
4
4
  def dump
5
- fail NotImplementedError 'dump method must be implemented'
5
+ fail NotImplementedError, 'dump method must be implemented'
6
6
  end
7
7
 
8
8
  def self.included(klass)
@@ -10,8 +10,8 @@ module Twterm
10
10
  end
11
11
 
12
12
  module ClassMethods
13
- def recover(title, arg)
14
- tab = new(arg)
13
+ def recover(app, client, title, arg)
14
+ tab = new(app, client, arg)
15
15
  tab.title = title
16
16
  tab
17
17
  end