diff --git a/Classes/ComOmorandiSMSDialogProxy.m b/Classes/ComOmorandiSMSDialogProxy.m index 20becea..49f150a 100644 --- a/Classes/ComOmorandiSMSDialogProxy.m +++ b/Classes/ComOmorandiSMSDialogProxy.m @@ -3,11 +3,17 @@ #import "TiColor.h" #import "TiApp.h" +@interface ComOmorandiSMSDialogProxy() + +@property (nonatomic, assign) BOOL attachmentsDisabled; +@property (nonatomic, retain) NSMutableArray *attachments; + +@end + + @implementation ComOmorandiSMSDialogProxy -/* - Proxy memory management -*/ + - (void) dealloc { @@ -15,12 +21,6 @@ - (void) dealloc [super dealloc]; } --(void)_destroy -{ - RELEASE_TO_NIL(recipients); - [super _destroy]; -} - // Get the recipients array @@ -45,6 +45,65 @@ -(void)addRecipient:(id)aRecipient } +-(BOOL)attachmentsSupported +{ + Class messageComposerClass = [self messageComposerClass]; + if (messageComposerClass == nil) { + return NO; + } + if (![messageComposerClass respondsToSelector:@selector(canSendAttachments)]) { + return NO; + } + return [NSNumber numberWithBool:[messageComposerClass canSendAttachments]]; +} + + +-(Class)messageComposerClass +{ + return NSClassFromString(@"MFMessageComposeViewController"); +} + +-(id)canSendAttachments:(id)args +{ + return [NSNumber numberWithBool:[self attachmentsSupported]]; +} + +-(void)disableUserAttachments:(id)args +{ + self.attachmentsDisabled = YES; +} + +-(void)addAttachment:(id)args +{ + if (![self attachmentsSupported]) { + [self throwException:@"sending attachments is not supported on the current device" subreason:nil location:CODELOCATION]; + } + if ([args count] < 1) { + [self throwException:@"expected string or TiBlob argument" subreason:nil location:CODELOCATION]; + } + + id attachment = [args objectAtIndex:0]; + + if (!([attachment isKindOfClass:[NSString class]] || [attachment isKindOfClass:[TiBlob class]])) { + [self throwException:@"expected string or TiBlob argument" subreason:nil location:CODELOCATION]; + } + + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + [dict setObject:attachment forKey:@"attachment"]; + + if ([args count] > 1) { + NSString *alternateFileName = [args objectAtIndex:1]; + ENSURE_TYPE(alternateFileName, NSString); + + [dict setObject:alternateFileName forKey:@"alternateFileName"]; + } + if (self.attachments == nil) { + self.attachments = [NSMutableArray array]; + } + + [self.attachments addObject:dict]; +} + // Check if the device provides sms sending capabilities - (id)isSupported:(id)args { @@ -57,7 +116,7 @@ - (id)isSupported:(id)args BOOL smsSupported = NO; //First we check the existence of the MFMessageComposeViewController class - Class messageClass = (NSClassFromString(@"MFMessageComposeViewController")); + Class messageClass = [self messageComposerClass]; if (messageClass != nil) { @@ -74,8 +133,10 @@ - (id)isSupported:(id)args // create and open the SMS dialog window - (void)open:(id)args { + ENSURE_SINGLE_ARG_OR_NIL(args, NSDictionary); + [self rememberSelf]; - ENSURE_SINGLE_ARG_OR_NIL(args, NSDictionary); + // ensure that the functionality is supported if (![self isSupported:nil]) @@ -123,7 +184,25 @@ - (void)open:(id)args //set our proxy as delegate (for responding to messageComposeViewController:didFinishWithResult) composer.messageComposeDelegate = self; - + + if (self.attachmentsDisabled) { + [composer disableUserAttachments]; + } + + if ([self.attachments count] > 0) { + for (NSDictionary *dict in self.attachments) { + id attachment = dict[@"attachment"]; + NSString *alternateFileName = dict[@"alternateFileName"]; + if ([attachment isKindOfClass:[NSString class]]) { + [composer addAttachmentURL:[TiUtils toURL:attachment proxy:self] withAlternateFilename:alternateFileName]; + } + else if ([attachment isKindOfClass:[TiBlob class]]){ + [composer addAttachmentData:[attachment data] typeIdentifier:[attachment mimeType] filename:alternateFileName]; + } + } + } + + //set the navbar color if (barColor != nil) { @@ -203,7 +282,8 @@ - (void)messageComposeViewController:(MFMessageComposeViewController *)composer [composer dismissViewControllerAnimated:YES completion:nil]; [self forgetSelf]; - [self autorelease];} + [self autorelease]; +} diff --git a/README.md b/README.md index 82f7796..a9c625a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ The SMSDialog Module extends the Appcelerator Titanium Mobile framework implementing an iPhone dialog window that enables you to send in application text messages on behalf of the app user, exposing an API very similar to the one of the Ti.UI.EmailDialog object. Both recipients and body fields can be pre-populated from your application. + +# New in Version 1.1.0: attachments support + +![](./screenshots/mms.png) + +Since iOS version 7.0, the `MFMessageComposeViewController` class allows adding attachments to SMS or iMessage messages, thus enabling the ability to send MMSs. +The SMSDialog module has been extended in order to support this feature through new API methods and properties: + +* `canSendAttachments()`: return true if the current device/os combination allows sending attachments +* `disableUserAttachments()`: Disables the camera/attachment button in the message composition view +* `addAttachment(attachment, alternateFileName)`: add a new attachment as a string file path, or TiBlob, optionally setting an alternate file name for it + + + ## Building and installing the SMSDialog Module ## ### BUILD ### @@ -11,12 +25,12 @@ First, you must have your XCode and Titanium Mobile SDKs in place, and have at l The build process can be launched using the build.py script that you find in the module's code root directory. -As a result, the `com.omorandi-iphone-1.0.2.zip` file will be generated. +As a result, the `com.omorandi-iphone-1.1.0.zip` file will be generated. ### INSTALL ### -You can either copy the module package (`com.omorandi-iphone-1.0.2.zip`) to `~/Library/Application\ Support/Titanium` and reference the module in your application (the Titanium SDK will automatically unzip the file in the right place), or manually launch the command: +You can either copy the module package (`com.omorandi-iphone-1.1.0.zip`) to `~/Library/Application\ Support/Titanium` and reference the module in your application (the Titanium SDK will automatically unzip the file in the right place), or manually launch the command: - unzip -u -o com.omorandi-iphone-1.0.2.zip -d ~/Library/Application\ Support/Titanium/ + unzip -u -o com.omorandi-iphone-1.1.0.zip -d ~/Library/Application\ Support/Titanium/ ## Referencing the module in your Titanium Mobile application ## @@ -24,7 +38,7 @@ You can either copy the module package (`com.omorandi-iphone-1.0.2.zip`) to `~/L Simply add the following lines to your `tiapp.xml` file: - com.omorandi + com.omorandi @@ -108,52 +122,68 @@ a string containing a textual description of the result ## Usage - //instantiate the module - var module = require('com.omorandi'); - Ti.API.info("module is => " + module); - - //create the smsDialog object - var smsDialog = module.createSMSDialog(); - - //check if the feature is available on the device at hand - if (!smsDialog.isSupported()) - { - //falls here when executed on iOS versions < 4.0 and in the emulator - var a = Ti.UI.createAlertDialog({title: 'warning', message: 'the required feature is not available on your device'}); - a.show(); +```JavaScript +//instantiate the module +var module = require('com.omorandi'); +Ti.API.info("module is => " + module); + +//create the smsDialog object +var smsDialog = module.createSMSDialog(); + +//check if the feature is available on the device at hand +if (!smsDialog.isSupported()) +{ + //falls here when executed on iOS versions < 4.0 and in the emulator + var a = Ti.UI.createAlertDialog({title: 'warning', message: 'the required feature is not available on your device'}); + a.show(); +} +else +{ + //pre-populate the dialog with the info provided in the following properties + smsDialog.recipients = ['+14151234567']; + smsDialog.messageBody = 'Test message from me'; + + //set the color of the title-bar + smsDialog.barColor = 'red'; + + //add attachments if supported + if (smsDialog.canSendAttachments()) { + Ti.API.info('We can send attachments'); + //add an attachment as a file path + smsDialog.addAttachment('images/01.jpg', 'image1.jpg'); + + var file = Ti.Filesystem.getFile('images/02.jpg'); + //add an attachment as a TiBlob + smsDialog.addAttachment(file.read(), 'image2.jpg'); } - else - { - //pre-populate the dialog with the info provided in the following properties - smsDialog.recipients = ['+14151234567']; - smsDialog.messageBody = 'Test message from me'; - - //set the color of the title-bar - smsDialog.barColor = 'red'; - - //add an event listener for the 'complete' event, in order to be notified about the result of the operation - smsDialog.addEventListener('complete', function(e){ - Ti.API.info("Result: " + e.resultMessage); - var a = Ti.UI.createAlertDialog({title: 'complete', message: 'Result: ' + e.resultMessage}); - a.show(); - if (e.result == smsDialog.SENT) - { - //do something - } - else if (e.result == smsDialog.FAILED) - { - //do something else - } - else if (e.result == smsDialog.CANCELLED) - { - //don't bother - } - }); - - //open the SMS dialog window with slide-up animation - smsDialog.open({animated: true}); + else { + Ti.API.info('We cannot send attachments'); } + //add an event listener for the 'complete' event, in order to be notified about the result of the operation + smsDialog.addEventListener('complete', function(e){ + Ti.API.info("Result: " + e.resultMessage); + var a = Ti.UI.createAlertDialog({title: 'complete', message: 'Result: ' + e.resultMessage}); + a.show(); + if (e.result == smsDialog.SENT) + { + //do something + } + else if (e.result == smsDialog.FAILED) + { + //do something else + } + else if (e.result == smsDialog.CANCELLED) + { + //don't bother + } + }); + + //open the SMS dialog window with slide-up animation + smsDialog.open({animated: true}); +} + +``` ## Author diff --git a/build_debug.py b/build_debug.py new file mode 100755 index 0000000..cd363d2 --- /dev/null +++ b/build_debug.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +# +# Appcelerator Titanium Module Packager +# +# +import os, sys, glob, string +import zipfile + +cwd = os.path.abspath(os.path.dirname(sys._getframe(0).f_code.co_filename)) +required_module_keys = ['name','version','moduleid','description','copyright','license','copyright','platform','minsdk'] +module_defaults = { + 'description':'My module', + 'author': 'Your Name', + 'license' : 'Specify your license', + 'copyright' : 'Copyright (c) 2010 by Your Company', +} +module_license_default = "TODO: place your license here and we'll include it in the module distribution" + +def replace_vars(config,token): + idx = token.find('$(') + while idx != -1: + idx2 = token.find(')',idx+2) + if idx2 == -1: break + key = token[idx+2:idx2] + if not config.has_key(key): break + token = token.replace('$(%s)' % key, config[key]) + idx = token.find('$(') + return token + + +def read_ti_xcconfig(): + contents = open(os.path.join(cwd,'titanium.xcconfig')).read() + config = {} + for line in contents.splitlines(False): + line = line.strip() + if line[0:2]=='//': continue + idx = line.find('=') + if idx > 0: + key = line[0:idx].strip() + value = line[idx+1:].strip() + config[key] = replace_vars(config,value) + return config + +def generate_doc(config): + docdir = os.path.join(cwd,'documentation') + if not os.path.exists(docdir): + print "Couldn't find documentation file at: %s" % docdir + return None + sdk = config['TITANIUM_SDK'] + support_dir = os.path.join(sdk,'module','support') + sys.path.append(support_dir) + import markdown2 + documentation = [] + for file in os.listdir(docdir): + md = open(os.path.join(docdir,file)).read() + html = markdown2.markdown(md) + documentation.append({file:html}); + return documentation + +def compile_js(manifest,config): + js_file = os.path.join(cwd,'assets','com.omorandi.js') + if not os.path.exists(js_file): return + + sdk = config['TITANIUM_SDK'] + iphone_dir = os.path.join(sdk,'iphone') + sys.path.insert(0,iphone_dir) + from compiler import Compiler + + path = os.path.basename(js_file) + metadata = Compiler.make_function_from_file(path,js_file) + method = metadata['method'] + eq = path.replace('.','_') + method = ' return %s;' % method + + f = os.path.join(cwd,'Classes','ComOmorandiModuleAssets.m') + c = open(f).read() + idx = c.find('return ') + before = c[0:idx] + after = """ +} + +@end + """ + newc = before + method + after + + if newc!=c: + x = open(f,'w') + x.write(newc) + x.close() + +def die(msg): + print msg + sys.exit(1) + +def warn(msg): + print "[WARN] %s" % msg + +def validate_license(): + c = open('LICENSE').read() + if c.find(module_license_default)!=1: + warn('please update the LICENSE file with your license text before distributing') + +def validate_manifest(): + path = os.path.join(cwd,'manifest') + f = open(path) + if not os.path.exists(path): die("missing %s" % path) + manifest = {} + for line in f.readlines(): + line = line.strip() + if line[0:1]=='#': continue + if line.find(':') < 0: continue + key,value = line.split(':') + manifest[key.strip()]=value.strip() + for key in required_module_keys: + if not manifest.has_key(key): die("missing required manifest key '%s'" % key) + if module_defaults.has_key(key): + defvalue = module_defaults[key] + curvalue = manifest[key] + if curvalue==defvalue: warn("please update the manifest key: '%s' to a non-default value" % key) + return manifest,path + +ignoreFiles = ['.DS_Store','.gitignore','libTitanium.a','titanium.jar','README','com.omorandi.js'] +ignoreDirs = ['.DS_Store','.svn','.git','CVSROOT'] + +def zip_dir(zf,dir,basepath,ignore=[]): + for root, dirs, files in os.walk(dir): + for name in ignoreDirs: + if name in dirs: + dirs.remove(name) # don't visit ignored directories + for file in files: + if file in ignoreFiles: continue + e = os.path.splitext(file) + if len(e)==2 and e[1]=='.pyc':continue + from_ = os.path.join(root, file) + to_ = from_.replace(dir, basepath, 1) + zf.write(from_, to_) + +def glob_libfiles(): + files = [] + for libfile in glob.glob('build/**/*.a'): + if libfile.find('Debug-')!=-1: + files.append(libfile) + return files + +def build_module(manifest,config): + rc = os.system("xcodebuild -sdk iphoneos -configuration Debug") + if rc != 0: + die("xcodebuild failed") + rc = os.system("xcodebuild -sdk iphonesimulator -configuration Debug") + if rc != 0: + die("xcodebuild failed") + # build the merged library using lipo + moduleid = manifest['moduleid'] + libpaths = '' + for libfile in glob_libfiles(): + libpaths+='%s ' % libfile + + os.system("lipo %s -create -output build/lib%s.a" %(libpaths,moduleid)) + +def package_module(manifest,mf,config): + name = manifest['name'].lower() + moduleid = manifest['moduleid'].lower() + version = manifest['version'] + modulezip = '%s-iphone-%s.zip' % (moduleid,version) + if os.path.exists(modulezip): os.remove(modulezip) + zf = zipfile.ZipFile(modulezip, 'w', zipfile.ZIP_DEFLATED) + modulepath = 'modules/iphone/%s/%s' % (moduleid,version) + zf.write(mf,'%s/manifest' % modulepath) + libname = 'lib%s.a' % moduleid + zf.write('build/%s' % libname, '%s/%s' % (modulepath,libname)) + for dn in ('assets','example'): + if os.path.exists(dn): + zip_dir(zf,dn,'%s/%s' % (modulepath,dn),['README']) + zf.write('LICENSE','%s/LICENSE' % modulepath) + zf.write('module.xcconfig','%s/module.xcconfig' % modulepath) + zf.close() + + +if __name__ == '__main__': + manifest,mf = validate_manifest() + validate_license() + config = read_ti_xcconfig() + compile_js(manifest,config) + build_module(manifest,config) + package_module(manifest,mf,config) + sys.exit(0) + diff --git a/com.omorandi-iphone-1.0.2.zip b/com.omorandi-iphone-1.0.2.zip deleted file mode 100644 index 744182c..0000000 Binary files a/com.omorandi-iphone-1.0.2.zip and /dev/null differ diff --git a/com.omorandi-iphone-1.1.0.zip b/com.omorandi-iphone-1.1.0.zip new file mode 100644 index 0000000..d03f960 Binary files /dev/null and b/com.omorandi-iphone-1.1.0.zip differ diff --git a/manifest b/manifest index b0c1524..94b9404 100644 --- a/manifest +++ b/manifest @@ -2,7 +2,7 @@ # this is your module manifest and used by Titanium # during compilation, packaging, distribution, etc. # -version: 1.0.2 +version: 1.1.0 description: SMSDialog module author: Olivier Morandi license: MIT license diff --git a/screenshots/mms.png b/screenshots/mms.png new file mode 100644 index 0000000..3383c23 Binary files /dev/null and b/screenshots/mms.png differ