Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Refs #264

 * Re-design direct message UI

git-svn-id: http://naan.net/svn/trunk/TwitterFon@1533 542e9493-1a22-0410-9183-c10453d2b9ee
  • Loading branch information...
commit f35719d59deb63f147d14159ab11c9d14d0ec703 1 parent bc86d4c
kaz authored

Showing 28 changed files with 1,864 additions and 136 deletions. Show diff stats Hide diff stats

  1. +22 0 Classes/Controllers/ChatBubbleCell.h
  2. +50 0 Classes/Controllers/ChatBubbleCell.m
  3. +23 0 Classes/Controllers/ChatBubbleView.h
  4. +65 0 Classes/Controllers/ChatBubbleView.m
  5. +21 0 Classes/Controllers/DMConversationController.h
  6. +134 0 Classes/Controllers/DMConversationController.m
  7. +35 0 Classes/Controllers/DMTimelineController.h
  8. +393 0 Classes/Controllers/DMTimelineController.m
  9. +21 0 Classes/Controllers/DMTimelineDataSource.h
  10. +224 0 Classes/Controllers/DMTimelineDataSource.m
  11. +19 0 Classes/Controllers/DirectMessageCell.h
  12. +47 0 Classes/Controllers/DirectMessageCell.m
  13. +18 0 Classes/Controllers/DirectMessageCellView.h
  14. +67 0 Classes/Controllers/DirectMessageCellView.m
  15. +16 10 Classes/Models/DBConnection.m
  16. +44 0 Classes/Models/DirectMessage.h
  17. +271 0 Classes/Models/DirectMessage.m
  18. +1 1  Classes/Models/Tweet.h
  19. +1 0  Classes/OtherSources/ColorUtils.h
  20. +9 5 Classes/OtherSources/ColorUtils.m
  21. BIN  Images/Balloon_2.png
  22. +168 0 Interfaces/DirectMessage.xib
  23. +52 92 Interfaces/MainWindow.xib
  24. +26 19 Interfaces/TimelineView.xib
  25. +68 0 TwitterFon.xcodeproj/project.pbxproj
  26. +3 9 create.sql
  27. BIN  db1.3.sql
  28. +66 0 update_v12_to_v13.sql
22 Classes/Controllers/ChatBubbleCell.h
... ... @@ -0,0 +1,22 @@
  1 +//
  2 +// ChatBubbleCell.h
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +
  11 +@class DirectMessage;
  12 +@class ChatBubbleView;
  13 +
  14 +@interface ChatBubbleCell : UITableViewCell
  15 +{
  16 + DirectMessage* message;
  17 + ChatBubbleView* cellView;
  18 +}
  19 +
  20 +- (void)setMessage:(DirectMessage*)msg isOwn:(BOOL)isOwnMessage;
  21 +
  22 +@end
50 Classes/Controllers/ChatBubbleCell.m
... ... @@ -0,0 +1,50 @@
  1 +//
  2 +// ChatBubbleCell.m
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import "ChatBubbleCell.h"
  10 +#import "DirectMessage.h"
  11 +#import "ChatBubbleView.h"
  12 +
  13 +@implementation ChatBubbleCell
  14 +
  15 +- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier
  16 +{
  17 + [super initWithFrame:frame reuseIdentifier:reuseIdentifier];
  18 +
  19 + cellView = [[[ChatBubbleView alloc] initWithFrame:CGRectZero] autorelease];
  20 + [self.contentView addSubview:cellView];
  21 +
  22 + return self;
  23 +}
  24 +
  25 +- (void)setMessage:(DirectMessage*)aMessage isOwn:(BOOL)isOwn
  26 +{
  27 + self.text = message.text;
  28 + message = aMessage;
  29 + cellView.type = isOwn;
  30 +}
  31 +
  32 +- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
  33 +
  34 + [super setSelected:selected animated:animated];
  35 +
  36 + // Configure the view for the selected state
  37 +}
  38 +
  39 +- (void)layoutSubviews
  40 +{
  41 + [super layoutSubviews];
  42 + cellView.frame = self.bounds;
  43 + self.backgroundColor = [UIColor clearColor];
  44 +}
  45 +
  46 +- (void)dealloc {
  47 + [super dealloc];
  48 +}
  49 +
  50 +@end
23 Classes/Controllers/ChatBubbleView.h
... ... @@ -0,0 +1,23 @@
  1 +//
  2 +// ChatBubbleView.h
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +
  11 +typedef enum {
  12 + BUBBLE_TYPE_GRAY,
  13 + BUBBLE_TYPE_GREEN,
  14 +} BubbleType;
  15 +
  16 +@interface ChatBubbleView : UIView
  17 +{
  18 + BubbleType type;
  19 +}
  20 +
  21 +@property(nonatomic, assign) BubbleType type;
  22 +
  23 +@end
65 Classes/Controllers/ChatBubbleView.m
... ... @@ -0,0 +1,65 @@
  1 +//
  2 +// ChatBubbleView.m
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import "ChatBubbleView.h"
  10 +static UIImage* sGreenBubble = nil;
  11 +static UIImage* sGrayBubble = nil;
  12 +
  13 +@interface ChatBubbleView(Private)
  14 ++ (UIImage*)greenBubble;
  15 ++ (UIImage*)grayBubble;
  16 +@end
  17 +
  18 +@implementation ChatBubbleView
  19 +
  20 +@synthesize type;
  21 +
  22 +- (id)initWithFrame:(CGRect)frame
  23 +{
  24 + if (self = [super initWithFrame:frame]) {
  25 + self.backgroundColor = [UIColor clearColor];
  26 + }
  27 + return self;
  28 +}
  29 +
  30 +- (void)drawRect:(CGRect)rect
  31 +{
  32 + UIImage *background;
  33 + if (type == BUBBLE_TYPE_GRAY) {
  34 + background = [ChatBubbleView grayBubble];
  35 + }
  36 + else {
  37 + background = [ChatBubbleView greenBubble];
  38 + }
  39 + [background drawInRect:rect];
  40 +}
  41 +
  42 +
  43 +- (void)dealloc {
  44 + [super dealloc];
  45 +}
  46 +
  47 ++ (UIImage*)greenBubble
  48 +{
  49 + if (sGreenBubble == nil) {
  50 + UIImage *i = [UIImage imageNamed:@"Balloon_1.png"];
  51 + sGreenBubble = [[i stretchableImageWithLeftCapWidth:15 topCapHeight:13] retain];
  52 + }
  53 + return sGreenBubble;
  54 +}
  55 +
  56 ++ (UIImage*)grayBubble
  57 +{
  58 + if (sGrayBubble == nil) {
  59 + UIImage *i = [UIImage imageNamed:@"Balloon_2.png"];
  60 + sGrayBubble = [[i stretchableImageWithLeftCapWidth:21 topCapHeight:13] retain];
  61 + }
  62 + return sGrayBubble;
  63 +}
  64 +
  65 +@end
21 Classes/Controllers/DMConversationController.h
... ... @@ -0,0 +1,21 @@
  1 +//
  2 +// DMConversationController.h
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +
  11 +@class DirectMessage;
  12 +
  13 +@interface DMConversationController : UITableViewController
  14 +{
  15 + NSMutableArray* messages;
  16 + DirectMessage* firstMessage;
  17 +}
  18 +
  19 +- (id)initWithMessage:(DirectMessage*)message;
  20 +
  21 +@end
134 Classes/Controllers/DMConversationController.m
... ... @@ -0,0 +1,134 @@
  1 +//
  2 +// DMConversationController.m
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import "TwitterFonAppDelegate.h"
  10 +#import "DMConversationController.h"
  11 +#import "DirectMessage.h"
  12 +#import "ColorUtils.h"
  13 +#import "ChatBubbleCell.h"
  14 +
  15 +@implementation DMConversationController
  16 +
  17 +- (id)initWithMessage:(DirectMessage*)msg
  18 +{
  19 + self = [super initWithStyle:UITableViewStylePlain];
  20 + self.navigationItem.title = msg.senderScreenName;
  21 + self.tableView.separatorColor = [UIColor conversationBackground];
  22 + self.tableView.backgroundColor = [UIColor conversationBackground];
  23 +
  24 + UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(postTweet:)];
  25 + self.navigationItem.rightBarButtonItem = button;
  26 +
  27 +// self.navigationItem.rightBarButtonItem = self.editButtonItem;
  28 +
  29 + messages = [[NSMutableArray alloc] init];
  30 + firstMessage = msg;
  31 + [DirectMessage getConversation:msg.senderId messages:messages all:false];
  32 +
  33 + return self;
  34 +}
  35 +
  36 + - (void)viewWillAppear:(BOOL)animated
  37 +{
  38 + [super viewWillAppear:animated];
  39 + self.navigationController.navigationBar.tintColor = nil;
  40 + }
  41 +/*
  42 + - (void)viewDidAppear:(BOOL)animated {
  43 + [super viewDidAppear:animated];
  44 + }
  45 + */
  46 +/*
  47 + - (void)viewWillDisappear:(BOOL)animated {
  48 + }
  49 + */
  50 +/*
  51 + - (void)viewDidDisappear:(BOOL)animated {
  52 + }
  53 + */
  54 +/*
  55 + - (void)didReceiveMemoryWarning {
  56 + [super didReceiveMemoryWarning];
  57 + }
  58 + */
  59 +
  60 +- (void)dealloc {
  61 + [messages release];
  62 + [super dealloc];
  63 +}
  64 +
  65 +
  66 +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  67 + return 1;
  68 +}
  69 +
  70 +
  71 +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  72 +{
  73 + return [messages count];
  74 +}
  75 +
  76 +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  77 +{
  78 + static NSString *CellIdentifier = @"ChatBubble";
  79 +
  80 + ChatBubbleCell *cell = (ChatBubbleCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  81 + if (cell == nil) {
  82 + cell = [[[ChatBubbleCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
  83 + }
  84 +
  85 + DirectMessage *dm = [messages objectAtIndex:indexPath.row];
  86 +
  87 + [cell setMessage:dm isOwn:dm.senderId != firstMessage.senderId];
  88 + cell.text = dm.text;
  89 + cell.font = [UIFont systemFontOfSize:16];
  90 +// cell.image = [[TwitterFonAppDelegate getAppDelegate].imageStore getProfileImage:dm.profileImageUrl delegate:dm];
  91 + // Configure the cell
  92 + return cell;
  93 +}
  94 +
  95 +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  96 +{
  97 + [tableView deselectRowAtIndexPath:indexPath animated:YES];
  98 +}
  99 +
  100 +/*
  101 +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
  102 +
  103 + if (editingStyle == UITableViewCellEditingStyleDelete) {
  104 + }
  105 + if (editingStyle == UITableViewCellEditingStyleInsert) {
  106 + }
  107 +}
  108 +*/
  109 +
  110 +/*
  111 +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
  112 + return YES;
  113 +}
  114 +*/
  115 +
  116 +/*
  117 +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
  118 +}
  119 +*/
  120 +
  121 +/*
  122 +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
  123 + return YES;
  124 +}
  125 +*/
  126 +
  127 +- (void)postTweet:(id)sender
  128 +{
  129 + PostViewController* postView = [TwitterFonAppDelegate getAppDelegate].postView;
  130 + [postView editDirectMessage:self.navigationItem.title];
  131 +}
  132 +
  133 +@end
  134 +
35 Classes/Controllers/DMTimelineController.h
... ... @@ -0,0 +1,35 @@
  1 +//
  2 +// FriendsTimelineController.h
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 10/29/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +
  11 +@class Tweet;
  12 +@class TwitterClient;
  13 +@class LoadCell;
  14 +
  15 +@interface DMTimelineController : UITableViewController
  16 +{
  17 + NSMutableArray* timeline;
  18 + NSMutableDictionary* messages;
  19 + TwitterClient* twitterClient;
  20 + LoadCell* loadCell;
  21 + int unread;
  22 + BOOL isLoaded;
  23 + BOOL firstTimeToAppear;
  24 + BOOL isRestored;
  25 + BOOL needToGetSentMessage;
  26 + CGPoint contentOffset;
  27 +}
  28 +
  29 +- (void)loadTimeline;
  30 +- (void)restoreAndLoadTimeline:(BOOL)load;
  31 +- (void)postTweetDidSucceed:(Tweet*)status;
  32 +
  33 +- (IBAction)reload:(id)sender;
  34 +
  35 +@end
393 Classes/Controllers/DMTimelineController.m
... ... @@ -0,0 +1,393 @@
  1 +//
  2 +// FriendsTimelineController.m
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 10/29/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import "DMTimelineController.h"
  10 +#import "DMConversationController.h"
  11 +#import "DirectMessage.h"
  12 +#import "DirectMessageCell.h"
  13 +#import "TwitterFonAppDelegate.h"
  14 +#import "DBConnection.h"
  15 +#import "Tweet.h"
  16 +#import "LoadCell.h"
  17 +#import "TwitterClient.h"
  18 +#import "ColorUtils.h"
  19 +
  20 +// sort function of DM timeline
  21 +//
  22 +NSInteger sortByDate(id a, id b, void *context)
  23 +{
  24 + DirectMessage* dma = (DirectMessage*)a;
  25 + DirectMessage* dmb = (DirectMessage*)b;
  26 + int diff = dmb.createdAt - dma.createdAt;
  27 + if (diff > 0)
  28 + return 1;
  29 + else if (diff < 0)
  30 + return -1;
  31 + else
  32 + return 0;
  33 +}
  34 +
  35 +@interface DMTimelineController(Private)
  36 +- (void)scrollToFirstUnread:(NSTimer*)timer;
  37 +- (void)getMessage:(BOOL)getSentMessage;
  38 +- (void)restoreMessage:(BOOL)restoreAll;
  39 +@end
  40 +
  41 +@implementation DMTimelineController
  42 +
  43 +- (void)awakeFromNib
  44 +{
  45 + loadCell = [[LoadCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"LoadCell"];
  46 + timeline = [[NSMutableArray alloc] init];
  47 + messages = [[NSMutableDictionary alloc] init];
  48 + [loadCell setType:MSG_TYPE_LOAD_FROM_DB];
  49 +}
  50 +
  51 +//
  52 +// UIViewController methods
  53 +//
  54 +- (void)viewDidLoad
  55 +{
  56 + if (!isLoaded) {
  57 + [self loadTimeline];
  58 + }
  59 +}
  60 +
  61 +- (void) dealloc
  62 +{
  63 + [twitterClient cancel];
  64 + [twitterClient release];
  65 + [loadCell release];
  66 + [timeline release];
  67 + [messages release];
  68 + [super dealloc];
  69 +}
  70 +
  71 +- (void)didReceiveMemoryWarning
  72 +{
  73 + TwitterFonAppDelegate *appDelegate = (TwitterFonAppDelegate*)[UIApplication sharedApplication].delegate;
  74 + if (appDelegate.selectedTab != [self navigationController].tabBarItem.tag) {
  75 + [super didReceiveMemoryWarning];
  76 + }
  77 +}
  78 +
  79 +- (void)viewWillAppear:(BOOL)animated
  80 +{
  81 + [super viewWillAppear:animated];
  82 +
  83 + [self.tableView setContentOffset:contentOffset animated:false];
  84 + [self.tableView reloadData];
  85 + self.navigationController.navigationBar.tintColor = [UIColor navigationColorForTab:TAB_MESSAGES];
  86 + self.tableView.separatorColor = [UIColor lightGrayColor];
  87 +
  88 +}
  89 +
  90 +- (void)viewDidAppear:(BOOL)animated
  91 +{
  92 + if (firstTimeToAppear) {
  93 + firstTimeToAppear = false;
  94 + [self scrollToFirstUnread:nil];
  95 + }
  96 + [super viewDidAppear:animated];
  97 +}
  98 +
  99 +- (void)viewWillDisappear:(BOOL)animated
  100 +{
  101 + contentOffset = self.tableView.contentOffset;
  102 +}
  103 +
  104 +- (void)viewDidDisappear:(BOOL)animated
  105 +{
  106 +}
  107 +
  108 +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  109 +{
  110 + return 1;
  111 +}
  112 +
  113 +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  114 +{
  115 + int count = [timeline count];
  116 + return (isRestored) ? count : count + 1;
  117 +}
  118 +
  119 +//
  120 +// UITableViewDelegate
  121 +//
  122 +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  123 +{
  124 + if (indexPath.row == [timeline count]) return 78;
  125 +
  126 + return 48 + 3 + 16;
  127 +}
  128 +
  129 +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  130 +{
  131 + if (indexPath.row == [timeline count]) {
  132 + return loadCell;
  133 + }
  134 + DirectMessage *dm = [timeline objectAtIndex:indexPath.row];
  135 +
  136 + DirectMessageCell *cell = (DirectMessageCell*)[tableView dequeueReusableCellWithIdentifier:@"DirectMessage"];
  137 + if (!cell) {
  138 + cell = [[[DirectMessageCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"DirectMessage"] autorelease];
  139 + }
  140 + [cell setMessage:dm];
  141 + return cell;
  142 +}
  143 +
  144 +
  145 +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  146 +{
  147 + [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
  148 +
  149 + if (indexPath.row >= [timeline count]) {
  150 + [self restoreMessage:true];
  151 + isRestored = true;
  152 + [tableView reloadData];
  153 + }
  154 + else {
  155 + DirectMessage *dm = [timeline objectAtIndex:indexPath.row];
  156 + DMConversationController *conv = [[[DMConversationController alloc] initWithMessage:dm] autorelease];
  157 + [self.navigationController pushViewController:conv animated:true];
  158 + }
  159 +}
  160 +
  161 +//
  162 +// Public methods
  163 +//
  164 +
  165 +- (void)loadTimeline
  166 +{
  167 + NSString *username = [[NSUserDefaults standardUserDefaults] stringForKey:@"username"];
  168 + NSString *password = [[NSUserDefaults standardUserDefaults] stringForKey:@"password"];
  169 + if (!(username == nil || password == nil ||
  170 + [username length] == 0 || [password length] == 0)) {
  171 + self.navigationItem.leftBarButtonItem.enabled = false;
  172 + [self getMessage:false];
  173 + }
  174 + isLoaded = true;
  175 +}
  176 +
  177 +- (void)restoreMessage:(BOOL)restoreAll
  178 +{
  179 + isRestored = restoreAll;
  180 + [timeline removeAllObjects];
  181 + [messages removeAllObjects];
  182 + [DirectMessage restore:timeline all:restoreAll];
  183 + for (int i = 0; i < [timeline count]; ++i) {
  184 + DirectMessage *dm = [timeline objectAtIndex:i];
  185 + [messages setObject:dm forKey:[NSString stringWithFormat:@"%d", dm.senderId]];
  186 + }
  187 +}
  188 +
  189 +- (void)restoreAndLoadTimeline:(BOOL)load
  190 +{
  191 + firstTimeToAppear = true;
  192 + [self restoreMessage:false];
  193 + if (load) [self loadTimeline];
  194 +}
  195 +
  196 +- (void)getMessage:(BOOL)getSentMessage
  197 +{
  198 + if (twitterClient) return;
  199 + twitterClient = [[TwitterClient alloc] initWithTarget:self action:@selector(messageDidReceive:obj:)];
  200 +
  201 + NSMutableDictionary *param = [NSMutableDictionary dictionary];
  202 +
  203 + if ([timeline count]) {
  204 + DirectMessage *dm = [timeline objectAtIndex:0];
  205 + int since_id = dm.messageId;
  206 +
  207 + [param setObject:[NSString stringWithFormat:@"%d", since_id] forKey:@"since_id"];
  208 + [param setObject:@"200" forKey:@"count"];
  209 + }
  210 + else {
  211 + [param setObject:@"200" forKey:@"count"];
  212 + }
  213 +
  214 + [twitterClient getTimeline:TWEET_TYPE_MESSAGES params:param];
  215 +
  216 + needToGetSentMessage = getSentMessage;
  217 +}
  218 +
  219 +- (IBAction)reload:(id) sender
  220 +{
  221 + self.navigationItem.leftBarButtonItem.enabled = false;
  222 + [self getMessage:true];
  223 +}
  224 +
  225 +- (void)autoRefresh
  226 +{
  227 + self.navigationItem.leftBarButtonItem.enabled = false;
  228 + [self getMessage:false];
  229 + [self reload:nil];
  230 +}
  231 +
  232 +
  233 +- (NSArray*)examObject:(NSObject*)obj
  234 +{
  235 + if (obj == nil) {
  236 + return nil;
  237 + }
  238 +
  239 + NSArray *ary = nil;
  240 + if ([obj isKindOfClass:[NSArray class]]) {
  241 + ary = (NSArray*)obj;
  242 + NSLog(@"Received %d messages", [ary count]);
  243 + return ary;
  244 + }
  245 + return nil;
  246 +}
  247 +
  248 +- (void)messageDidReceive:(TwitterClient*)sender obj:(NSObject*)obj
  249 +{
  250 + twitterClient = nil;
  251 + [loadCell.spinner stopAnimating];
  252 + self.navigationItem.leftBarButtonItem.enabled = true;
  253 +
  254 + if (needToGetSentMessage) {
  255 + self.navigationItem.leftBarButtonItem.enabled = false;
  256 + twitterClient = [[TwitterClient alloc] initWithTarget:self action:@selector(sentMessageDidReceived:obj:)];
  257 + NSMutableDictionary *param = [NSMutableDictionary dictionary];
  258 + [param setObject:@"200" forKey:@"count"];
  259 + [twitterClient getTimeline:TWEET_TYPE_SENT params:nil];
  260 + }
  261 +
  262 + NSArray *ary = [self examObject:obj];
  263 + if (ary == nil) return;
  264 +
  265 + DirectMessage* lastDM = [timeline lastObject];
  266 + if ([ary count] == 0) return;
  267 +
  268 + // Retrieve DM from JSON object then insert them
  269 + sqlite3* database = [DBConnection getSharedDatabase];
  270 + char *errmsg;
  271 + sqlite3_exec(database, "BEGIN", NULL, NULL, &errmsg);
  272 +
  273 + for (int i = [ary count] - 1; i >= 0; --i) {
  274 + NSDictionary *dic = (NSDictionary*)[ary objectAtIndex:i];
  275 + if (![dic isKindOfClass:[NSDictionary class]]) {
  276 + continue;
  277 + }
  278 + sqlite_int64 aId = [[[ary objectAtIndex:i] objectForKey:@"id"] longLongValue];
  279 + if (![DirectMessage isExists:aId]) {
  280 + DirectMessage* dm = [DirectMessage messageWithJsonDictionary:[ary objectAtIndex:i]];
  281 + if (dm.createdAt < lastDM.createdAt) {
  282 + // Ignore stale message
  283 + continue;
  284 + }
  285 + [dm insertDB];
  286 + dm.unread = true;
  287 + [messages setObject:dm forKey:[NSString stringWithFormat:@"%d", dm.senderId]];
  288 + ++unread;
  289 + }
  290 + }
  291 + sqlite3_exec(database, "COMMIT", NULL, NULL, &errmsg);
  292 +
  293 + [timeline release];
  294 + timeline = [[messages allValues] mutableCopy];
  295 + NSLog(@"Updated %d messages", [timeline count]);
  296 + [timeline sortUsingFunction:sortByDate context:nil];
  297 +
  298 + [self.tableView reloadData];
  299 +
  300 + if (unread) {
  301 + [self navigationController].tabBarItem.badgeValue = [NSString stringWithFormat:@"%d", unread];
  302 + }
  303 +}
  304 +
  305 +- (void)sentMessageDidReceived:(TwitterClient*)sender obj:(NSObject*)obj
  306 +{
  307 + self.navigationItem.leftBarButtonItem.enabled = true;
  308 + twitterClient = nil;
  309 +
  310 + NSArray *ary = [self examObject:obj];
  311 + if (ary == nil) return;
  312 +
  313 + // Retrieve DM from JSON object then insert them
  314 + sqlite3* database = [DBConnection getSharedDatabase];
  315 + char *errmsg;
  316 + sqlite3_exec(database, "BEGIN", NULL, NULL, &errmsg);
  317 +
  318 + for (int i = [ary count] - 1; i >= 0; --i) {
  319 + NSDictionary *dic = (NSDictionary*)[ary objectAtIndex:i];
  320 + if (![dic isKindOfClass:[NSDictionary class]]) {
  321 + continue;
  322 + }
  323 + sqlite_int64 aId = [[[ary objectAtIndex:i] objectForKey:@"id"] longLongValue];
  324 + if (![DirectMessage isExists:aId]) {
  325 + DirectMessage* dm = [DirectMessage messageWithJsonDictionary:[ary objectAtIndex:i]];
  326 + [dm insertDB];
  327 + }
  328 + }
  329 + sqlite3_exec(database, "COMMIT", NULL, NULL, &errmsg);
  330 +}
  331 +
  332 +- (void)twitterClientDidFail:(TwitterClient*)sender error:(NSString*)error detail:(NSString*)detail
  333 +{
  334 + twitterClient = nil;
  335 + [loadCell.spinner stopAnimating];
  336 + self.navigationItem.leftBarButtonItem.enabled = true;
  337 +
  338 + [TwitterFonAppDelegate alert:error message:detail];
  339 +}
  340 +
  341 +
  342 +- (void)postViewAnimationDidFinish
  343 +{
  344 + if (self.navigationController.topViewController != self) return;
  345 +
  346 + NSArray *indexPaths = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], nil];
  347 + [self.tableView beginUpdates];
  348 + [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
  349 + [self.tableView endUpdates];
  350 +
  351 +}
  352 +
  353 +- (void)postTweetDidSucceed:(Tweet*)status
  354 +{
  355 + // TO BE IMPLEMENTED
  356 +}
  357 +//
  358 +// TwitterFonApPDelegate delegate
  359 +//
  360 +- (void)didLeaveTab:(UINavigationController*)navigationController
  361 +{
  362 + navigationController.tabBarItem.badgeValue = nil;
  363 + for (DirectMessage *dm in timeline) {
  364 + dm.unread = false;
  365 + }
  366 + for (DirectMessage *dm in [messages allValues]) {
  367 + dm.unread = false;
  368 + }
  369 + unread = 0;
  370 +}
  371 +
  372 +
  373 +- (void) removeMessage:(DirectMessage*)message
  374 +{
  375 + [messages removeObjectForKey:[NSString stringWithFormat:@"%d", message.senderId]];
  376 + [timeline removeObject:message];
  377 + [self.tableView reloadData];
  378 +}
  379 +
  380 +- (void)scrollToFirstUnread:(NSTimer*)timer
  381 +{
  382 +#if 0
  383 + if (unread) {
  384 + if (unread < [timeline count]) {
  385 + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:unread inSection:0];
  386 + [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition: UITableViewScrollPositionBottom animated:true];
  387 + }
  388 + }
  389 +#endif
  390 +}
  391 +
  392 +@end
  393 +
21 Classes/Controllers/DMTimelineDataSource.h
... ... @@ -0,0 +1,21 @@
  1 +//
  2 +// FriendsTimelineDataSource.h
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/14/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +#import <UIKit/UIKit.h>
  9 +#import "TimelineDataSource.h"
  10 +
  11 +@interface FriendsTimelineDataSource : TimelineDataSource <UITableViewDataSource, UITableViewDelegate> {
  12 + UITableViewController* controller;
  13 + int insertPosition;
  14 + float content
  15 + BOOL isRestored;
  16 +}
  17 +
  18 +- (id)initWithController:(UITableViewController*)controller;
  19 +- (void)getTimeline;
  20 +
  21 +@end
224 Classes/Controllers/DMTimelineDataSource.m
... ... @@ -0,0 +1,224 @@
  1 +//
  2 +// FriendsTimelineDataSource.m
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/14/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import <QuartzCore/QuartzCore.h>
  10 +#import "FriendsTimelineDataSource.h"
  11 +#import "TwitterFonAppDelegate.h"
  12 +#import "TweetViewController.h"
  13 +#import "ProfileViewController.h"
  14 +
  15 +#import "TimelineCell.h"
  16 +#import "DBConnection.h"
  17 +
  18 +static UIAlertView* sAlert = nil;
  19 +
  20 +@interface NSObject (TimelineViewControllerDelegate)
  21 +- (void)timelineDidUpdate:(FriendsTimelineDataSource*)sender count:(int)count insertAt:(int)position;
  22 +- (void)timelineDidFailToUpdate:(FriendsTimelineDataSource*)sender position:(int)position;
  23 +@end
  24 +
  25 +@implementation FriendsTimelineDataSource
  26 +
  27 +@synthesize timeline;
  28 +@synthesize contentOffset;
  29 +
  30 +
  31 +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  32 +{
  33 + return 1;
  34 +}
  35 +
  36 +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  37 +{
  38 + int count = [timeline countStatuses];
  39 + return (isRestored) ? count : count + 1;
  40 +}
  41 +
  42 +//
  43 +// UITableViewDelegate
  44 +//
  45 +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  46 +{
  47 + Status* sts = [timeline statusAtIndex:indexPath.row];
  48 + return sts ? sts.cellHeight : 78;
  49 +
  50 +}
  51 +
  52 +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  53 +{
  54 +
  55 + TimelineCell* cell = [timeline getTimelineCell:tableView atIndex:indexPath.row];
  56 + if (cell) {
  57 + return cell;
  58 + }
  59 + else {
  60 + return loadCell;
  61 + }
  62 +}
  63 +
  64 +
  65 +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  66 +{
  67 + Status* sts = [timeline statusAtIndex:indexPath.row];
  68 +
  69 + if (sts) {
  70 + // Display user view
  71 + //
  72 + if (tweetType == TWEET_TYPE_MESSAGES || tweetType == TWEET_TYPE_SENT) {
  73 + ProfileViewController *profile = [[[ProfileViewController alloc] initWithProfile:sts.user] autorelease];
  74 + [[controller navigationController] pushViewController:profile animated:true];
  75 + }
  76 + else {
  77 + TweetViewController* tweetView = [[[TweetViewController alloc] initWithMessage:sts] autorelease];
  78 + [[controller navigationController] pushViewController:tweetView animated:TRUE];
  79 + }
  80 + }
  81 + else {
  82 + // Restore tweets from DB
  83 + //
  84 + int count = [timeline restore:tweetType all:true];
  85 + isRestored = true;
  86 +
  87 + NSMutableArray *newPath = [[[NSMutableArray alloc] init] autorelease];
  88 +
  89 + [tableView beginUpdates];
  90 + // Avoid to create too many table cell.
  91 + if (count > 0) {
  92 + if (count > 2) count = 2;
  93 + for (int i = 0; i < count; ++i) {
  94 + [newPath addObject:[NSIndexPath indexPathForRow:i + indexPath.row inSection:0]];
  95 + }
  96 + [tableView insertRowsAtIndexPaths:newPath withRowAnimation:UITableViewRowAnimationTop];
  97 + }
  98 + else {
  99 + [newPath addObject:indexPath];
  100 + [tableView deleteRowsAtIndexPaths:newPath withRowAnimation:UITableViewRowAnimationLeft];
  101 + }
  102 + [tableView endUpdates];
  103 + }
  104 +
  105 + [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
  106 +}
  107 +
  108 +
  109 +- (void)getTimeline
  110 +{
  111 + if (twitterClient) return;
  112 + twitterClient = [[TwitterClient alloc] initWithTarget:self action:@selector(timelineDidReceive:obj:)];
  113 +
  114 + insertPosition = 0;
  115 +
  116 + NSMutableDictionary *param = [NSMutableDictionary dictionary];
  117 +
  118 + int since_id = 0;
  119 + NSString *username = [[NSUserDefaults standardUserDefaults] stringForKey:@"username"];
  120 + for (int i = 0; i < [timeline countStatuses]; ++i) {
  121 + Status* sts = [timeline statusAtIndex:i];
  122 + if ([sts.user.screenName caseInsensitiveCompare:username] != NSOrderedSame) {
  123 + since_id = sts.statusId;
  124 + break;
  125 + }
  126 + }
  127 +
  128 + if (since_id) {
  129 + [param setObject:[NSString stringWithFormat:@"%d", since_id] forKey:@"since_id"];
  130 + [param setObject:@"200" forKey:@"count"];
  131 + }
  132 +
  133 + [twitterClient getTimeline:tweetType params:param];
  134 +}
  135 +
  136 +- (void)timelineDidReceive:(TwitterClient*)sender obj:(NSObject*)obj
  137 +{
  138 + twitterClient = nil;
  139 + [loadCell.spinner stopAnimating];
  140 +
  141 + if (obj == nil) {
  142 + return;
  143 + }
  144 +
  145 + NSArray *ary = nil;
  146 + if ([obj isKindOfClass:[NSArray class]]) {
  147 + ary = (NSArray*)obj;
  148 + }
  149 + else {
  150 + return;
  151 + }
  152 +
  153 + int unread = 0;
  154 + NSLog(@"Received %d messages on tab %d", [ary count], tweetType);
  155 +
  156 + Status* lastStatus = [timeline lastStatus];
  157 + if ([ary count]) {
  158 + sqlite3* database = [DBConnection getSharedDatabase];
  159 + char *errmsg;
  160 + sqlite3_exec(database, "BEGIN", NULL, NULL, &errmsg);
  161 +
  162 + // Add messages to the timeline
  163 + for (int i = [ary count] - 1; i >= 0; --i) {
  164 + NSDictionary *dic = (NSDictionary*)[ary objectAtIndex:i];
  165 + if (![dic isKindOfClass:[NSDictionary class]]) {
  166 + continue;
  167 + }
  168 + sqlite_int64 statusId = [[[ary objectAtIndex:i] objectForKey:@"id"] longLongValue];
  169 + if (![Status isExists:statusId type:tweetType]) {
  170 + Status* sts = [Status statusWithJsonDictionary:[ary objectAtIndex:i] type:tweetType];
  171 + if (sts.createdAt < lastStatus.createdAt) {
  172 + // Ignore stale message
  173 + continue;
  174 + }
  175 + [sts insertDB];
  176 + sts.unread = true;
  177 +
  178 + [timeline insertStatus:sts atIndex:insertPosition];
  179 + ++unread;
  180 + }
  181 + }
  182 +
  183 + sqlite3_exec(database, "COMMIT", NULL, NULL, &errmsg);
  184 + }
  185 +
  186 + if ([controller respondsToSelector:@selector(timelineDidUpdate:count:insertAt:)]) {
  187 + [controller timelineDidUpdate:self count:unread insertAt:insertPosition];
  188 + }
  189 +}
  190 +
  191 +- (void)twitterClientDidFail:(TwitterClient*)sender error:(NSString*)error detail:(NSString*)detail
  192 +{
  193 + twitterClient = nil;
  194 + [loadCell.spinner stopAnimating];
  195 +
  196 + if ([controller respondsToSelector:@selector(timelineDidFailToUpdate:position:)]) {
  197 + [controller timelineDidFailToUpdate:self position:insertPosition];
  198 + }
  199 +
  200 + if (sender.statusCode == 401) {
  201 + TwitterFonAppDelegate *appDelegate = (TwitterFonAppDelegate*)[UIApplication sharedApplication].delegate;
  202 + [appDelegate openSettingsView];
  203 + }
  204 +
  205 + //
  206 + // Save alert view to static pointer to avoid displaying alert view many times at the same time.
  207 + //
  208 + if (sAlert) return;
  209 +
  210 + sAlert = [[UIAlertView alloc] initWithTitle:error
  211 + message:detail
  212 + delegate:self
  213 + cancelButtonTitle:@"Close"
  214 + otherButtonTitles: nil];
  215 + [sAlert show];
  216 + [sAlert release];
  217 +}
  218 +
  219 +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonInde
  220 +{
  221 + sAlert = nil;
  222 +}
  223 +
  224 +@end
19 Classes/Controllers/DirectMessageCell.h
... ... @@ -0,0 +1,19 @@
  1 +//
  2 +// DirectMessageCell.h
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +#import "DirectMessageCellView.h"
  11 +#import "DirectMessage.h"
  12 +
  13 +@interface DirectMessageCell : UITableViewCell {
  14 + DirectMessageCellView* view;
  15 +}
  16 +
  17 +- (void)setMessage:(DirectMessage*)message;
  18 +
  19 +@end
47 Classes/Controllers/DirectMessageCell.m
... ... @@ -0,0 +1,47 @@
  1 +//
  2 +// DirectMessageCell.m
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import "DirectMessageCell.h"
  10 +#import "TwitterFonAppDelegate.h"
  11 +#import "ColorUtils.h"
  12 +
  13 +@implementation DirectMessageCell
  14 +
  15 +- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
  16 + if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) {
  17 +
  18 + self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  19 + view = [[DirectMessageCellView alloc] initWithFrame:CGRectZero];
  20 + [self.contentView addSubview:view];
  21 + self.contentView.backgroundColor = [UIColor whiteColor];
  22 + [view release];
  23 + }
  24 + return self;
  25 +}
  26 +
  27 +- (void)setMessage:(DirectMessage*)value
  28 +{
  29 + view.message = value;
  30 + self.contentView.backgroundColor = value.unread ? [UIColor cellColorForTab:TAB_MESSAGES] : [UIColor whiteColor];
  31 + self.image = [[TwitterFonAppDelegate getAppDelegate].imageStore getProfileImage:value.senderProfileImageUrl delegate:value];
  32 +}
  33 +
  34 +- (void)layoutSubviews
  35 +{
  36 + [super layoutSubviews];
  37 + view.frame = CGRectMake(48 + 20, 0, 320 - 48 - 20 - 10, 48 + 16 + 2);
  38 + self.backgroundColor = self.contentView.backgroundColor;
  39 + view.backgroundColor = self.contentView.backgroundColor;
  40 +}
  41 +
  42 +- (void)dealloc {
  43 + [super dealloc];
  44 +}
  45 +
  46 +
  47 +@end
18 Classes/Controllers/DirectMessageCellView.h
... ... @@ -0,0 +1,18 @@
  1 +//
  2 +// DirectMessageCellView.h
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +#import "DirectMessage.h"
  11 +
  12 +@interface DirectMessageCellView : UIView {
  13 + DirectMessage* message;
  14 +}
  15 +
  16 +@property(nonatomic, retain) DirectMessage* message;
  17 +
  18 +@end
67 Classes/Controllers/DirectMessageCellView.m
... ... @@ -0,0 +1,67 @@
  1 +//
  2 +// DirectMessageCellView.m
  3 +// TwitterFon
  4 +//
  5 +// Created by kaz on 12/17/08.
  6 +// Copyright 2008 naan studio. All rights reserved.
  7 +//
  8 +
  9 +#import "DirectMessageCellView.h"
  10 +
  11 +
  12 +@implementation DirectMessageCellView
  13 +
  14 +@synthesize message;
  15 +
  16 +- (id)initWithFrame:(CGRect)frame
  17 +{
  18 + if (self = [super initWithFrame:frame]) {
  19 + // Initialization code
  20 + }
  21 + return self;
  22 +}
  23 +
  24 +- (void)setMessage:(DirectMessage*)value