mealogでTwitpicに画像をアップロードする方法

mealogではTwitter投稿時に写真をアップロードできるのですが、わかりにくいと思うのでアップロード方法を紹介します。

写真をとってある食事でツイートしようとすると下記のような画面になると思います。
真ん中のバーの写真マークをタップすると、写真が表示されます。
写真の右下にUPLOADボタンがあるので、これをタップするとアップロードができ、アップロード先のURLが本文に反映されます。
meal画像アップロード

複数写真をアップロードしたい場合は、左右にスワイプすると別の写真が表示されるので、同じようにUPLOADボタンを押していきます。

わかり・・にくいですよね^^;;;
もっと直感的に、かつあまり手をかけずにできるように改善しようと思います。
最近あまりケアできてなかったですが、がんばります!

今後ともmealogをよろしくお願いします!

mealogがiTunes健康カテゴリで2位!総合で152位!順調に伸びてます!

iTunesトップ、最上段に掲載していただき順調に順位が伸びています!
ありがとうございます!
よりいっそう改善に努めていきます。

健康&フィットネスカテゴリランキング

総合ランキング

mealogがiTunes健康カテゴリで3位!総合で183位!

iTunesトップ掲載効果です!

健康&フィットネスカテゴリランキング

総合ランキング

mealogがiTunesトップの最上段に!

あのiMovieやストIVと同列に自分のアプリが紹介されるなんて思いもよりませんでした。
一人でアプリ開発をやっていると、時々不安になったりしますがこういったフィードバックがあると、いっそうやる気がでますね。
これもたくさんの方から意見をいただけてアップデートしてきた結果だと思います。
みなさまありがとうございます。

mealog2.0をリリースしました!

だいぶ時間がたってしまいましたが

ようやく2.0を出すことができました!
前回の更新が2月くらいだったので、4ヶ月も掛かってしまった。。すいません。
いろいろいじっているうちに深みにはまり、全体のバランスを見ているとなかなか出せない状況に。。
次回からはこまめに更新できるようにがんばります!

早速ですが、変更点について紹介させてください。

リスト表示

  • 月ごとに表示していたリストをシームレスに表示。一番したまでスクロールすると次の月を読み込みます。
  • 食事記録回数を表示

グラフ表示

  • 月ごとに表示していたグラフをシームレスに表示。左にスクロールさせると過去分のグラフが見れます。
  • 縦、横表示に対応

 

日別画面

  • 1日の中で、いつ食事をしたか一覧できるようにタイムスケールを表示
  • 写真サイズの拡大
  • すぐに撮影できるようにカメラボタンを配置
  • 体重のミニグラフ表示

編集画面

  • 下にバーを配置し、完了ボタンを押しやすくした

ツイッター

  • TwitPic投稿時にメッセージを追加
  • OAuth/XAuthによる認証に対応し、パスワードを保存しないようにしセキュリティ向上

他にもサポートページにスクリーンショットを載せているのでよかったら見てみてください!

mealog1.5以降のロードマップ

感謝

mealogの開発ですが、おかげさまで多くの方に使っていただけています。
日々ながめているTwitterのつぶやきや、AppStoreのレビュー、その他いろいろな声により
支えていただいています。
本当にありがとうございます。

また、同時に色々な希望や要望をいただいています。
より良いアプリを作っていくためにも、ロードマップを定めて
開発を進めていかなくてはと思っています。

長々とまとまらない文で申し訳ありませんが、一度考えていることを
公開しておいた方が良いと思い、書き出してみました。

悩み

そこで、まずいきなりですが、大きな悩みがあります。
色々な要望や、自分が実現したい機能はあるのですが、
それらを実装することによりアプリの過剰な複雑化、重たい動きに
なってしまわないか?という心配があります。

もちろんiPhoneの設計思想にもあるように、可能な限り機能をそぎ落として、
本当に必要な機能を実装したい、とは思っています。
自分もつい最近までiPhone3G(not 3GS)を使っていたので、
“3Gでも快適に動くアプリ”というのを一つの指針として持っています。
このシンプルさや、動作の軽快さを気に入っていただいている方も多いと思っています。
ただ多くの機能追加をすると、3Gでは確実に重くなるのは目に見えています。

開発を進めて行く中で判断していくと思うのですが、2つの方針があると思っています。
ひとつは可能なかぎり快適な動きをする中で、できうる限りの機能追加をする。
もう一つは別アプリとして高機能版を作ってしまうか。。
今の考えでは、従来使っていただいてる方へ余分な負担は避けたいため、
前者で進められればいいなと思っていますが、
追加する機能によっては後者を選択せざるをえないかもしれません。

ロードマップ

■現行ベースの改善
まずは要望も多いのと、次のステップでデータの持ち方を変更しようと思うので、
万一の事を考えてデータ関連の機能を追加しようと思っています。
データのインポート、エクスポート、バックアップなどです。

■大幅なアップデート
開発開始から時間が経っていて、より良い機能が使えるようになっている事、
はじめて開発したアプリで至らぬ点がまだまだある事から、
一回システムのベースの部分から作り直したいと思っています。
これはこれからの機能改善をスムーズにしていく中で、必要不可欠なことだと思っています。

具体的にはデータの持ち方や処理の方法の再構築、デザインやUIの部分の再構築、+アルファでTwitterなど他サービスとの連携部分の強化、などを考えています。

これにはまとまった時間が必要だと思われるので、しばらくは
小さな機能追加を行いつつも、平行して大幅アップデートを進めていこうと思っています。
 
 
長々と読んで頂きありがとうございます。
ご意見、ご要望などありましたらコメント欄やTwitter@malted_milkまたは
maltedmilk2(at)gmail.comまでご連絡いただければと思います。

mealog1.5を申請しました。

パスコードによるロック機能を追加したmealog1.5をAppleに申請しました。

設定画面

パスコードロックの設定を追加しました。

起動画面

ロックを有効にした状態で起動するとこんな感じでパスコード入力画面になります。

mealog1.4をリリースしました!

かなり久しぶりの投稿になってしまいました^^;
前回のアップデート報告が1.1なのでかなりさぼってますね。。すいません。
twitterばかりでブログがおろそかになってました。
これからはちゃんとリリース報告や、現在の開発状況や今後の展開などを
エントリーしていけたらなと思っています。

さて、mealogの1.4での追加機能について説明します。
 
 

体重・体脂肪率のTwitter投稿機能

体重入力の画面、右上のTweetボタンを押すと、右の画像のように投稿画面が現れます。
体重、体脂肪率ともに入力されているもののみ表示されます。
また、目標値が設定されている場合は目標までの値も自動で挿入されます。
 

 
 

食事内容入力の横移動を可能に。各入力画面にゴミ箱設置

真ん中のバーのボタンを押すことで、切り替えができます。
 
 

以上になります。
今後ともmealogをよろしくお願いします。

iPhoneアプリの売上をPushNotificationで受け取れるようにしてみた

普段からAppSalesというアプリで、日々の売上をチェックしているのですが、
iTunes Connectの更新時間が19:00〜20:00くらいとまちまちで、
処理が遅れてる時はそれよりも遅れることもあります。

まだ初アプリをだしてから半月というところなので、
毎日この時間帯になるとソワソワして、他の事が手につきません。
何回も更新ボタンを押し続けるのは時間の無駄なので、
売上情報が更新されたら、通知してくれるようにできないかなと考えてました。

なんとなく出来そうだったので、↓のような感じで実装してみました。

iTunes Connectからの情報取得

まずは、iTunesConnectからの情報の取得ですが、
AppSalesがやってるので、他の言語でもできるだろうなと思って、
既に他言語で公開されてないか調べてみました。

iPhoneアプリで稼げるのかで、Ruby版が公開されていました。
GoogleCodeでPython版を見つけました。

ただ普段はぺちぱーだったりするので、
PHPの方がやりやすいかなと思ってたらありました。

enormego.comでHeartbeatというアプリの売上を提供するサービスを
作っている方が公開してくれているようです。

Heartbeatと連携するスクリプトのようですが、
* Feel free to modify this as necessary to satisfy your needs.
と書いてくれているので、これを修正させてもらってiPhoneに通知させてみようと思います。
ソースコード

みなさまありがとうございます。

iPhoneへの通知(PushNotification)

データ取得はできそうなので、次は通知部分。

メールとかでもさくっとできそうですが、
せっかくiPhoneアプリ開発をしていて、AppSalesも使わせてもらっているので、
PushNotificationで通知を受け取り、そのままAppSalesを立ち上げるとうい
流れが作れないか考えてみました。

そこで、ちょっと前に知ったim.kayac.comという
PushNotificationを送ってくれるAPIを提供しているサービスを思い出しました。
これは何かに使える!と思って、アプリとアカウントを用意して試用したくらいで
しばらく放置していたものだったのですが、これしかない!と思いました。

PHPからこのAPIを叩く方法は、im.kayac.comからリンクされていた、
/halt/Snapshotさんの記事を参考にさせていただきました。
この方法の場合、アカウント設定でAPIの認証方式を”秘密鍵認証を使用する”にしておきます。

im.kayac.comでアカウントを作成し、
iPhoneアプリをインストールして設定します。
僕は最初から有料版を入れましたが、無料版もあったようです。
im.kayac.com:FREE Edition(iTunes)
im.kayac.com(iTunes)

AppSalesでURL Schemeを設定する

im.kayac.comの通知には、他のiPhoneアプリを立ち上げるためのURLを付加する事ができます。
なので、AppSales側でURL Schemeを設定してあげれば、
通知を受けてから、AppSalesをそのまま立ち上げる事が可能になります。

URL Schemeについては、まだ細かい部分は理解していないのですが、
ひとまずはinfo.plistに下記の様にURL Typesを追加すると
URL Schemesに設定した名前で呼び出せるようになるようです。
この場合だと、appsales:// で呼び出せます。

もっと細かく起動を制御する時はinfo.plistとアプリ側での開発も必要になるようです。
今回は起動までできれば不都合はないので、ここまでとします。

※こちらはもとまかさんの記事を参考にさせていただきました。ありがとうございます。

info.plist

まとめ

・im.kayac.comでアカウント登録、設定
・im.kayac.comのiPhoneアプリをインストールして設定
・下記スクリプトを実行する
と、すると
・iTunes Connectに前日分の売上データがあれば、iPhoneに通知が送られ、
 そのままAppSalesを立ち上げる事が可能になります。
・売上データ取得時に、ローカルに日付名のファイルを作ります。
 同日のファイルがあると、処理をスキップするので、
 このスクリプトをCronなどに登録して、データが反映されそうな時間帯で
 繰り返し実行させておけば、あまりタイムラグなく通知が受けられると思います。
 (あまり実行させすぎも迷惑だと思いますが)

PushNotification

PushNotification

im.kayac.com

im.kayac.com

Open AppSales

Open AppSales

スクリプト

作成した(といっても足し合わせただけすが)スクリプトを公開しておきます。
わりとやっつけでくっつけてしまっているので、
エラー処理や構成など機会を見て、キレイにしようと思います。
※細かい処理まで見きれていないので、ご利用は自己責任でお願いします。

<?php

/**
 * Date: Dec 19th, 2009
 * Copyright MaltedMilk. 2009 all rights reserved
 * 
 * このスクリプトは下記ソースコードを参考にしています。 
 * http://snippie.net/snip/f835bd0d 
 * http://project-p.jp/halt/anubis/blog_show/1269 
 * 
 */


// Run Artery
$run = new Artery('iTUNES_CONNECT_USERNAME', 'iTUNES_CONNECT_PASSWORD', 'KAYAC_USERNAME', 'KAYAC_PASSWORD', 'DATA_DIR');
unset($run);

/** 
 * There should be no need to modify anything below this line.
 */
class Artery {
    
    private $cookie;
    private $user_agent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_4_11; en) AppleWebKit/528.5+ (KHTML, like Gecko) Version/4.0 Safari/528.1';
    private $referrer = 'https://itts.apple.com/cgi-bin/WebObjects/Piano.woa/';

    private $username;
    private $password;

    private $kayac_username;
    private $kayac_password;
    
    private $curl_common = array();
    private $login_output; // Output from login method
    private $available_daily_reports_output; // Output from available_reports method
    private $wosid; // Session ID container
    private $keys = array(); // Form keys container

    public function __construct($username, $password, $kayac_username, $kayac_password, $data_dir = './') {
        // Set Some Data
        $this->username = $username;
        $this->password = $password;
        $this->kayac_username = $kayac_username;
        $this->kayac_password = $kayac_password;
        $this->data_file;

        // Setup cURL
        $this->setup_curl();

        // File Path
        $timestamp = strtotime('yesterday');
        $date = date('m/d/Y', $timestamp);

        $this->data_file = $data_dir . date('Y-m-d', $timestamp) . '.tsv';
        if (file_exists($this->data_file)) {
            exit(0);
        }

        // Login 
        if(!$this->login()) {
            exit('Login failed. Check your credentials and try again.');
        }
        
        // What's available to us? (Only if we need reports)
        $available = $this->available_daily_reports();

        // Get Report
        if(in_array($date, $available)) {
            $report = $this->report($date);
        }

        // Check Data
        if(stristr($report, 'Vendor Identifier')) {
            $this->pushNotification($report);
            $this->saveData($report);
        }
    }
    
    private function login() {
        // Get Login Form URL
        $result = $this->get('https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa');
        preg_match('#action="(.*?)"#ismu', $result, $matches);
        
        // Check for login url
        if(empty($matches[1])) {
            return false;
        }
        
        // Attempt Login
        $this->login_output = $this->post("https://itunesconnect.apple.com".$matches[1], array('theAccountName' => $this->username, 'theAccountPW' => $this->password));
        
        // Validate that we are indeed logged in
        if(!stristr($this->login_output, 'theAccountPW')) {
            $this->login_output = $this->get("https://itts.apple.com/cgi-bin/WebObjects/Piano.woa/");
            if(!stristr($this->login_output, 'theAccountPW')) {
                $this->logged_in = true;
                
                // Get Session ID
                preg_match('#name=\"wosid\" value=\"(.*?)\"#ismu', $this->login_output, $matches);
                $this->wosid = $matches[1];

                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
    
    public function available_daily_reports() {
        // Check login
        if(!$this->logged_in) {
            throw new Eight_Exception('You must run the login method before proceeding.');
        }
        
        // Find correct form names
        preg_match('#onchange\=\"handleSelReportType\(\)\" name\=\"(.*?)\"#ismu', $this->login_output, $matches);
        $this->keys['summary'] = $matches[1];

        preg_match('#onchange\=\"handleSelDateType\(\)\" name\=\"(.*?)\"#ismu', $this->login_output, $matches);
        $this->keys['daily'] = $matches[1];

        // Select Report
        $values = array();
        $values[$this->keys['summary']] = 'Summary';
        $values[$this->keys['daily']] = 'Daily';
        $values['hiddenDayOrWeekSelection'] = 'Daily';
        $values['hiddenSubmitTypeName'] = 'ShowDropDown';
        
        preg_match('#name="frmVendorPage" action="(.*?)"#ismu', $this->login_output, $matches);
        $this->available_daily_reports_output = $this->post("https://itts.apple.com".$matches[1], $values);

        preg_match('#\<select Id\=\"dayorweekdropdown\".*?\>(.*?)\<\/select\>#ismu', $this->available_daily_reports_output, $matches);
        preg_match_all('#\<option value\=\"(.*?)\"\>#ismu', $matches[1], $matches);
        return $matches[1];
    }
    
    public function report($date) {
        // Check login
        if(!$this->logged_in) {
            throw new Eight_Exception('You must run the login method before proceeding.');
        }
        
        // Find correct form keys
        preg_match('#onChange\=\"jsClearErrorMsg\(\);\" name\=\"(.*?)\"#ismu', $this->available_daily_reports_output, $matches);
        $this->keys['date'] = $matches[1];

        // Select Report
        $values = array();
        $values[$this->keys['daily']] = 'Daily';
        $values['hiddenDayOrWeekSelection'] = $date;
        $values['hiddenSubmitTypeName'] = 'Download';
        $values[$this->keys['summary']] = 'Summary';
        $values[$this->keys['date']] = $date;
        $values['download'] = 'Download';
        $values['wosid'] = $this->wosid;

        preg_match('#name="frmVendorPage" action="(.*?)"#ismu', $this->login_output, $matches);
        $output = $this->post("https://itts.apple.com".$matches[1], $values);

        return $this->gzuncompress($output);
    }
    
    private function get($url) {
        return $this->curl($url, $this->curl_common);
    }
    
    private function post($url, $data = array()) {
        $options = array(
                            CURLOPT_POSTFIELDS => http_build_query($data),
                            CURLOPT_POST => true,
                            CURLOPT_FOLLOWLOCATION => true,
                        );

        return $this->curl($url, $this->curl_common + $options);
    }
    
    private function curl($url, $options=array()) {
        $options = $options + array(CURLOPT_RETURNTRANSFER => true) + $this->curl_common;
        $c = curl_init($url);
        curl_setopt_array($c, $options);
        $output = curl_exec($c);
        curl_close($c);
        return $output;
    }
    
    private function setup_curl() {
        // Create Cookie
        $this->cookie = tempnam('/tmp', 'cookie');
        
        // Set cURL Options
        $this->curl_common = array(
                                        CURLOPT_USERAGENT => $this->user_agent, 
                                        CURLOPT_REFERER => $this->referrer, 
                                        CURLOPT_COOKIEJAR => $this->cookie, 
                                        CURLOPT_COOKIEFILE => $this->cookie,
                                        CURLOPT_SSL_VERIFYPEER =>    false,
                                        CURLOPT_HTTPHEADER => array("Expect:", 'Accept: */*', 'Connection: Keep-Alive'),
                                    );
    }
    
    // Custom gz uncompress because PHP's is broken
    private function gzuncompress($data) {
        $f = tempnam('/tmp', 'gz_fix');
        file_put_contents($f, $data);
        $uncompressed = file_get_contents('compress.zlib://'.$f);
        unlink($f);
        return $uncompressed;
    }
    
    public function __destruct() {
        // Delete Cookie
        unlink($this->cookie);
    }

    private function pushNotification($report) {
        $pushData = '';
        $records = explode("\n", $report);
        array_shift($records);
        foreach ($records as $record) {
            if (! $record) {
                continue;
            }

            $data = explode("\t", $record);
            $pushData .= sprintf("%s %s * %s = %s (%s)[%s]" . PHP_EOL,
                $data[6],  // Title
                $data[10], // Royality Price
                $data[9],  // Units
                $data[10] * $data[9],
                $data[15], // Royality Currency
                $data[14]  // Country Code
            );
        }

        $data = array(
            "message" => $pushData,
            "password" => $this->kayac_password,
            'handler' => 'appsales://',
        );

        $data['sig'] = sha1($data['message'] . $data['password']);
        unset($data['password']);

        $data = http_build_query($data, "", "&");

        //header
        $header = array(
            "Content-Type: application/x-www-form-urlencoded",
            "Content-Length: ".strlen($data)
        );

        $context = array(
            "http" => array(
                "method"  => "POST",
                "header"  => implode("\r\n", $header),
                "content" => $data
            )
        );

        $url = "http://im.kayac.com/api/post/{$this->kayac_username}";
        file_get_contents($url, false, stream_context_create($context));
    }

    private function saveData($report) {
        $result = file_put_contents($this->data_file, $report);
        if ($result === false) {
            echo "Can not write {$this->data_file}" . PHP_EOL;
            exit(1);
        }
    }
    
} // End Artery Class

Mealog1.1をリリースしました!

 
iPhoneのダイエットアプリ Mealogのバージョン1.1をリリースしました。
目標体重・体脂肪率が設定でき、グラフ表示できるようになっています。
 
Mealog
 

Mealogのグラフ

Mealogのグラフ

フォロー

Get every new post delivered to your Inbox.