From 8e6dbac183e1d88dde196f975f9160560443033a Mon Sep 17 00:00:00 2001 From: NMerz Date: Sat, 17 Oct 2020 22:56:25 -0400 Subject: [PATCH 01/46] Kohl's scraping outline Create an outline with some of the initial work needed to pull in data from Kohl's for populating the database, specifically, a non-durable extraction prototype and integration with a web requesting/scraping service --- Lambdas/Scraping/KohlsScraper.py | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Lambdas/Scraping/KohlsScraper.py diff --git a/Lambdas/Scraping/KohlsScraper.py b/Lambdas/Scraping/KohlsScraper.py new file mode 100644 index 0000000..f01fc9d --- /dev/null +++ b/Lambdas/Scraping/KohlsScraper.py @@ -0,0 +1,37 @@ +import requests + +headers = { + "apikey": "" +} + +params = ( + ("url","https://www.kohls.com/search.jsp?submit-search=web-regular&search=shoes"), + ("location","na"), +); + +response = requests.get('https://app.zenscrape.com/api/v1/get', headers=headers, params=params); +print(response.text) + +soup = BeautifulSoup(response.text, 'html.parser') + +for match in soup.find_all(id=re.compile(".*_prod_price")): + price = None + description = "" + match_split = match.text.split() + for section in match_split: + if '$' in section: + description = "" + if price == None: + price = section + continue + if ('(' in section) or (')' in section): + continue + description += section + " " + description = description.strip() + imgUrl = "" + imgUrlBase = 'https://media.kohlsimg.com/is/image/kohls/' + for prior in match.previous_siblings: + if imgUrlBase in str(prior): + imgUrl = imgUrlBase + str(prior).split(imgUrlBase)[1].split('?')[0].split('"')[0] + print(price + " for: " + description + " @: " + imgUrl) + From 7dbea8f80553b9edc395c44f1fd6606793991b1f Mon Sep 17 00:00:00 2001 From: NMerz Date: Sun, 18 Oct 2020 14:10:41 -0400 Subject: [PATCH 02/46] Add Kohls data to database Place data in the database when scraped Also include loading credentials from config files --- .gitignore | 2 + Lambdas/Scraping/KohlsScraper.py | 78 +++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 55ac339..4b21ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,5 @@ Lambdas/Lists/target/classes/META-INF/Lists.kotlin_module Listify/app/src/main/res/raw/auths.json Lambdas/Lists/target/surefire-reports/TestInputUtils.txt Lambdas/Lists/target/surefire-reports/TEST-TestInputUtils.xml +Lambdas/Scraping/scraperConfigs.json +Lambdas/Scraping/dbConfigs.json diff --git a/Lambdas/Scraping/KohlsScraper.py b/Lambdas/Scraping/KohlsScraper.py index f01fc9d..d5058fd 100644 --- a/Lambdas/Scraping/KohlsScraper.py +++ b/Lambdas/Scraping/KohlsScraper.py @@ -1,7 +1,16 @@ import requests +import json + +import pymysql.cursors +import time + +scraper_configs = None +with open("scraperConfigs.json", "r") as scraper_configs_file: + scraper_configs = json.load(scraper_configs_file) headers = { - "apikey": "" + "apikey": scraper_configs["apikey"] + } params = ( @@ -9,29 +18,54 @@ params = ( ("location","na"), ); -response = requests.get('https://app.zenscrape.com/api/v1/get', headers=headers, params=params); +response = requests.get("https://app.zenscrape.com/api/v1/get", headers=headers, params=params); print(response.text) -soup = BeautifulSoup(response.text, 'html.parser') +soup = BeautifulSoup(response.text, "html.parser") + +insert_params = [] for match in soup.find_all(id=re.compile(".*_prod_price")): - price = None - description = "" - match_split = match.text.split() - for section in match_split: - if '$' in section: - description = "" - if price == None: - price = section - continue - if ('(' in section) or (')' in section): - continue - description += section + " " - description = description.strip() - imgUrl = "" - imgUrlBase = 'https://media.kohlsimg.com/is/image/kohls/' - for prior in match.previous_siblings: - if imgUrlBase in str(prior): - imgUrl = imgUrlBase + str(prior).split(imgUrlBase)[1].split('?')[0].split('"')[0] - print(price + " for: " + description + " @: " + imgUrl) + price = None + description = "" + match_split = match.text.split() + for section in match_split: + if '$' in section: + description = "" + if price == None: + price = section + continue + if ('(' in section) or (')' in section): + continue + description += section + " " + description = description.strip() + imgUrl = "" + imgUrlBase = "https://media.kohlsimg.com/is/image/kohls/" + for prior in match.previous_siblings: + if imgUrlBase in str(prior): + imgUrl = imgUrlBase + str(prior).split(imgUrlBase)[1].split('?')[0].split('"')[0] + print(price + " for: " + description + " @: " + imgUrl) + insert_params.append((3, description, float(price.split('$')[1]), imgUrl)) +db_configs = None +with open("dbConfigs.json", "r") as db_configs_file: + db_configs = json.load(db_configs_file) + + +connection = pymysql.connect(host=db_configs["host"], + user=db_configs["user"], + password=db_configs["password"], + db=db_configs["db_name"], + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) + +try: + with connection.cursor() as cursor: + PRODUCT_INSERT_SYNTAX = "INSERT INTO Product (chainID, description, price, imageURL) VALUES (%s, %s, %s, %s);" + cursor.executemany(PRODUCT_INSERT_SYNTAX, insert_params) + connection.commit() +except Exception as e: + print(e) + traceback.print_exc() +finally: + connection.close() From 79598bf9e90eeeff3a339c03c2a52eab152db73b Mon Sep 17 00:00:00 2001 From: NMerz Date: Sun, 18 Oct 2020 17:51:08 -0400 Subject: [PATCH 03/46] Lambdaify Kohls scraping Move to Lambda function setup and add orchestration and word lists for full runs. Credit for the word lists goes to: http://www.desiquintans.com/nounlist --- .gitignore | 1 + Lambdas/Scraping/KohlsScraper.py | 119 +- Lambdas/Scraping/buildKohlsZip.sh | 8 + Lambdas/Scraping/nounlist.txt | 6801 +++++++++++++++++++++++ Lambdas/Scraping/prefix_list_builder.py | 25 + Lambdas/Scraping/prefix_list_part1.txt | 1 + Lambdas/Scraping/prefix_list_part2.txt | 1 + Lambdas/Scraping/runOrchestrator.py | 19 + Lambdas/Scraping/setupBuildFolder.sh | 7 + 9 files changed, 6927 insertions(+), 55 deletions(-) create mode 100644 Lambdas/Scraping/buildKohlsZip.sh create mode 100644 Lambdas/Scraping/nounlist.txt create mode 100644 Lambdas/Scraping/prefix_list_builder.py create mode 100644 Lambdas/Scraping/prefix_list_part1.txt create mode 100644 Lambdas/Scraping/prefix_list_part2.txt create mode 100644 Lambdas/Scraping/runOrchestrator.py create mode 100644 Lambdas/Scraping/setupBuildFolder.sh diff --git a/.gitignore b/.gitignore index 4b21ca3..ec00b98 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,4 @@ Lambdas/Lists/target/surefire-reports/TestInputUtils.txt Lambdas/Lists/target/surefire-reports/TEST-TestInputUtils.xml Lambdas/Scraping/scraperConfigs.json Lambdas/Scraping/dbConfigs.json +Lambdas/Scraping/artifacts/* diff --git a/Lambdas/Scraping/KohlsScraper.py b/Lambdas/Scraping/KohlsScraper.py index d5058fd..8dba24c 100644 --- a/Lambdas/Scraping/KohlsScraper.py +++ b/Lambdas/Scraping/KohlsScraper.py @@ -1,71 +1,80 @@ import requests import json +from bs4 import BeautifulSoup + import pymysql.cursors import time +import re -scraper_configs = None -with open("scraperConfigs.json", "r") as scraper_configs_file: - scraper_configs = json.load(scraper_configs_file) +def lambda_handler(event, context): + print(event["toScrape"]) + scraper_configs = None + with open("scraperConfigs.json", "r") as scraper_configs_file: + scraper_configs = json.load(scraper_configs_file) -headers = { - "apikey": scraper_configs["apikey"] - -} + headers = { + "apikey": scraper_configs["apikey"] + + } -params = ( - ("url","https://www.kohls.com/search.jsp?submit-search=web-regular&search=shoes"), - ("location","na"), -); + params = ( + ("url","https://www.kohls.com/search.jsp?submit-search=web-regular&search="+ event["toScrape"]), + ("location","na"), + ); -response = requests.get("https://app.zenscrape.com/api/v1/get", headers=headers, params=params); -print(response.text) + response = requests.get("https://app.zenscrape.com/api/v1/get", headers=headers, params=params) -soup = BeautifulSoup(response.text, "html.parser") + soup = BeautifulSoup(response.text, "html.parser") -insert_params = [] + insert_params = [] -for match in soup.find_all(id=re.compile(".*_prod_price")): - price = None - description = "" - match_split = match.text.split() - for section in match_split: - if '$' in section: - description = "" - if price == None: - price = section - continue - if ('(' in section) or (')' in section): - continue - description += section + " " - description = description.strip() - imgUrl = "" - imgUrlBase = "https://media.kohlsimg.com/is/image/kohls/" - for prior in match.previous_siblings: - if imgUrlBase in str(prior): - imgUrl = imgUrlBase + str(prior).split(imgUrlBase)[1].split('?')[0].split('"')[0] - print(price + " for: " + description + " @: " + imgUrl) - insert_params.append((3, description, float(price.split('$')[1]), imgUrl)) + for match in soup.find_all(id=re.compile(".*_prod_price")): + price = None + description = "" + match_split = match.text.split() + for section in match_split: + if '$' in section: + description = "" + if price == None: + price = section + continue + if ('(' in section) or (')' in section): + continue + description += section + " " + description = description.strip() + imgUrl = "" + imgUrlBase = "https://media.kohlsimg.com/is/image/kohls/" + for prior in match.previous_siblings: + if imgUrlBase in str(prior): + imgUrl = imgUrlBase + str(prior).split(imgUrlBase)[1].split('?')[0].split('"')[0] + print(price + " for: " + description + " @: " + imgUrl) + insert_params.append((3, description, float(price.split('$')[1]), imgUrl)) -db_configs = None -with open("dbConfigs.json", "r") as db_configs_file: - db_configs = json.load(db_configs_file) + db_configs = None + with open("dbConfigs.json", "r") as db_configs_file: + db_configs = json.load(db_configs_file) -connection = pymysql.connect(host=db_configs["host"], - user=db_configs["user"], - password=db_configs["password"], - db=db_configs["db_name"], - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor) + connection = pymysql.connect(host=db_configs["host"], + user=db_configs["user"], + password=db_configs["password"], + db=db_configs["db_name"], + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) -try: - with connection.cursor() as cursor: - PRODUCT_INSERT_SYNTAX = "INSERT INTO Product (chainID, description, price, imageURL) VALUES (%s, %s, %s, %s);" - cursor.executemany(PRODUCT_INSERT_SYNTAX, insert_params) - connection.commit() -except Exception as e: - print(e) - traceback.print_exc() -finally: - connection.close() + try: + with connection.cursor() as cursor: + PRODUCT_INSERT_SYNTAX = "INSERT INTO Product (chainID, description, price, imageURL) VALUES (%s, %s, %s, %s);" + cursor.executemany(PRODUCT_INSERT_SYNTAX, insert_params) + connection.commit() + except Exception as e: + print(e) + traceback.print_exc() + finally: + connection.close() + + return { + 'statusCode': 200, + 'body': 'Scraped: ' + event["toScrape"] + } diff --git a/Lambdas/Scraping/buildKohlsZip.sh b/Lambdas/Scraping/buildKohlsZip.sh new file mode 100644 index 0000000..59d85f6 --- /dev/null +++ b/Lambdas/Scraping/buildKohlsZip.sh @@ -0,0 +1,8 @@ +#!/bin/bash +#Currently to be run only from the Scraping directory +OLDPWD=$(pwd) +cd build/ +zip -r9 ${OLDPWD}/artifacts/kohlsScraper.zip . +cd ${OLDPWD} +zip -r9 artifacts/kohlsScraper.zip *.json +zip -r9 artifacts/kohlsScraper.zip KohlsScraper.py diff --git a/Lambdas/Scraping/nounlist.txt b/Lambdas/Scraping/nounlist.txt new file mode 100644 index 0000000..21e4bcb --- /dev/null +++ b/Lambdas/Scraping/nounlist.txt @@ -0,0 +1,6801 @@ +ATM +CD +SUV +TV +aardvark +abacus +abbey +abbreviation +abdomen +ability +abnormality +abolishment +abortion +abrogation +absence +abundance +abuse +academics +academy +accelerant +accelerator +accent +acceptance +access +accessory +accident +accommodation +accompanist +accomplishment +accord +accordance +accordion +account +accountability +accountant +accounting +accuracy +accusation +acetate +achievement +achiever +acid +acknowledgment +acorn +acoustics +acquaintance +acquisition +acre +acrylic +act +action +activation +activist +activity +actor +actress +acupuncture +ad +adaptation +adapter +addiction +addition +address +adjective +adjustment +admin +administration +administrator +admire +admission +adobe +adoption +adrenalin +adrenaline +adult +adulthood +advance +advancement +advantage +advent +adverb +advertisement +advertising +advice +adviser +advocacy +advocate +affair +affect +affidavit +affiliate +affinity +afoul +afterlife +aftermath +afternoon +aftershave +aftershock +afterthought +age +agency +agenda +agent +aggradation +aggression +aglet +agony +agreement +agriculture +aid +aide +aim +air +airbag +airbus +aircraft +airfare +airfield +airforce +airline +airmail +airman +airplane +airport +airship +airspace +alarm +alb +albatross +album +alcohol +alcove +alder +ale +alert +alfalfa +algebra +algorithm +alias +alibi +alien +allegation +allergist +alley +alliance +alligator +allocation +allowance +alloy +alluvium +almanac +almighty +almond +alpaca +alpenglow +alpenhorn +alpha +alphabet +altar +alteration +alternative +altitude +alto +aluminium +aluminum +amazement +amazon +ambassador +amber +ambience +ambiguity +ambition +ambulance +amendment +amenity +ammunition +amnesty +amount +amusement +anagram +analgesia +analog +analogue +analogy +analysis +analyst +analytics +anarchist +anarchy +anatomy +ancestor +anchovy +android +anesthesiologist +anesthesiology +angel +anger +angina +angiosperm +angle +angora +angstrom +anguish +animal +anime +anise +ankle +anklet +anniversary +announcement +annual +anorak +answer +ant +anteater +antecedent +antechamber +antelope +antennae +anterior +anthropology +antibody +anticipation +anticodon +antigen +antique +antiquity +antler +antling +anxiety +anybody +anyone +anything +anywhere +apartment +ape +aperitif +apology +app +apparatus +apparel +appeal +appearance +appellation +appendix +appetiser +appetite +appetizer +applause +apple +applewood +appliance +application +appointment +appreciation +apprehension +approach +appropriation +approval +apricot +apron +apse +aquarium +aquifer +arcade +arch +arch-rival +archaeologist +archaeology +archeology +archer +architect +architecture +archives +area +arena +argument +arithmetic +ark +arm +arm-rest +armadillo +armament +armchair +armoire +armor +armour +armpit +armrest +army +arrangement +array +arrest +arrival +arrogance +arrow +art +artery +arthur +artichoke +article +artifact +artificer +artist +ascend +ascent +ascot +ash +ashram +ashtray +aside +asparagus +aspect +asphalt +aspic +ass +assassination +assault +assembly +assertion +assessment +asset +assignment +assist +assistance +assistant +associate +association +assumption +assurance +asterisk +astrakhan +astrolabe +astrologer +astrology +astronomy +asymmetry +atelier +atheist +athlete +athletics +atmosphere +atom +atrium +attachment +attack +attacker +attainment +attempt +attendance +attendant +attention +attenuation +attic +attitude +attorney +attraction +attribute +auction +audience +audit +auditorium +aunt +authentication +authenticity +author +authorisation +authority +authorization +auto +autoimmunity +automation +automaton +autumn +availability +avalanche +avenue +average +avocado +award +awareness +awe +axis +azimuth +babe +baboon +babushka +baby +bachelor +back +back-up +backbone +backburn +backdrop +background +backpack +backup +backyard +bacon +bacterium +badge +badger +bafflement +bag +bagel +baggage +baggie +baggy +bagpipe +bail +bait +bake +baker +bakery +bakeware +balaclava +balalaika +balance +balcony +ball +ballet +balloon +balloonist +ballot +ballpark +bamboo +ban +banana +band +bandana +bandanna +bandolier +bandwidth +bangle +banjo +bank +bankbook +banker +banking +bankruptcy +banner +banquette +banyan +baobab +bar +barbecue +barbeque +barber +barbiturate +bargain +barge +baritone +barium +bark +barley +barn +barometer +barracks +barrage +barrel +barrier +barstool +bartender +base +baseball +baseboard +baseline +basement +basics +basil +basin +basis +basket +basketball +bass +bassinet +bassoon +bat +bath +bather +bathhouse +bathrobe +bathroom +bathtub +battalion +batter +battery +batting +battle +battleship +bay +bayou +beach +bead +beak +beam +bean +beancurd +beanie +beanstalk +bear +beard +beast +beastie +beat +beating +beauty +beaver +beck +bed +bedrock +bedroom +bee +beech +beef +beer +beet +beetle +beggar +beginner +beginning +begonia +behalf +behavior +behaviour +beheading +behest +behold +being +belfry +belief +believer +bell +belligerency +bellows +belly +belt +bench +bend +beneficiary +benefit +beret +berry +best-seller +bestseller +bet +beverage +beyond +bias +bibliography +bicycle +bid +bidder +bidding +bidet +bifocals +bijou +bike +bikini +bill +billboard +billing +billion +bin +binoculars +biology +biopsy +biosphere +biplane +birch +bird +bird-watcher +birdbath +birdcage +birdhouse +birth +birthday +biscuit +bit +bite +bitten +bitter +black +blackberry +blackbird +blackboard +blackfish +blackness +bladder +blade +blame +blank +blanket +blast +blazer +blend +blessing +blight +blind +blinker +blister +blizzard +block +blocker +blog +blogger +blood +bloodflow +bloom +bloomer +blossom +blouse +blow +blowgun +blowhole +blue +blueberry +blush +boar +board +boat +boatload +boatyard +bob +bobcat +body +bog +bolero +bolt +bomb +bomber +bombing +bond +bonding +bondsman +bone +bonfire +bongo +bonnet +bonsai +bonus +boogeyman +book +bookcase +bookend +booking +booklet +bookmark +boolean +boom +boon +boost +booster +boot +bootee +bootie +booty +border +bore +borrower +borrowing +bosom +boss +botany +bother +bottle +bottling +bottom +bottom-line +boudoir +bough +boulder +boulevard +boundary +bouquet +bourgeoisie +bout +boutique +bow +bower +bowl +bowler +bowling +bowtie +box +boxer +boxspring +boy +boycott +boyfriend +boyhood +boysenberry +bra +brace +bracelet +bracket +brain +brake +bran +branch +brand +brandy +brass +brassiere +bratwurst +bread +breadcrumb +breadfruit +break +breakdown +breakfast +breakpoint +breakthrough +breast +breastplate +breath +breeze +brewer +bribery +brick +bricklaying +bride +bridge +brief +briefing +briefly +briefs +brilliant +brink +brisket +broad +broadcast +broccoli +brochure +brocolli +broiler +broker +bronchitis +bronco +bronze +brooch +brood +brook +broom +brother +brother-in-law +brow +brown +brownie +browser +browsing +brunch +brush +brushfire +brushing +bubble +buck +bucket +buckle +buckwheat +bud +buddy +budget +buffalo +buffer +buffet +bug +buggy +bugle +builder +building +bulb +bulk +bull +bull-fighter +bulldozer +bullet +bump +bumper +bun +bunch +bungalow +bunghole +bunkhouse +burden +bureau +burglar +burial +burlesque +burn +burn-out +burning +burrito +burro +burrow +burst +bus +bush +business +businessman +bust +bustle +butane +butcher +butler +butter +butterfly +button +buy +buyer +buying +buzz +buzzard +c-clamp +cabana +cabbage +cabin +cabinet +cable +caboose +cacao +cactus +caddy +cadet +cafe +caffeine +caftan +cage +cake +calcification +calculation +calculator +calculus +calendar +calf +caliber +calibre +calico +call +calm +calorie +camel +cameo +camera +camp +campaign +campaigning +campanile +camper +campus +can +canal +cancer +candelabra +candidacy +candidate +candle +candy +cane +cannibal +cannon +canoe +canon +canopy +cantaloupe +canteen +canvas +cap +capability +capacity +cape +caper +capital +capitalism +capitulation +capon +cappelletti +cappuccino +captain +caption +captor +car +carabao +caramel +caravan +carbohydrate +carbon +carboxyl +card +cardboard +cardigan +care +career +cargo +caribou +carload +carnation +carnival +carol +carotene +carp +carpenter +carpet +carpeting +carport +carriage +carrier +carrot +carry +cart +cartel +carter +cartilage +cartload +cartoon +cartridge +carving +cascade +case +casement +cash +cashew +cashier +casino +casket +cassava +casserole +cassock +cast +castanet +castle +casualty +cat +catacomb +catalogue +catalysis +catalyst +catamaran +catastrophe +catch +catcher +category +caterpillar +cathedral +cation +catsup +cattle +cauliflower +causal +cause +causeway +caution +cave +caviar +cayenne +ceiling +celebration +celebrity +celeriac +celery +cell +cellar +cello +celsius +cement +cemetery +cenotaph +census +cent +center +centimeter +centre +centurion +century +cephalopod +ceramic +ceramics +cereal +ceremony +certainty +certificate +certification +cesspool +chafe +chain +chainstay +chair +chairlift +chairman +chairperson +chaise +chalet +chalice +chalk +challenge +chamber +champagne +champion +championship +chance +chandelier +change +channel +chaos +chap +chapel +chaplain +chapter +character +characteristic +characterization +chard +charge +charger +charity +charlatan +charm +charset +chart +charter +chasm +chassis +chastity +chasuble +chateau +chatter +chauffeur +chauvinist +check +checkbook +checking +checkout +checkroom +cheddar +cheek +cheer +cheese +cheesecake +cheetah +chef +chem +chemical +chemistry +chemotaxis +cheque +cherry +chess +chest +chestnut +chick +chicken +chicory +chief +chiffonier +child +childbirth +childhood +chili +chill +chime +chimpanzee +chin +chinchilla +chino +chip +chipmunk +chit-chat +chivalry +chive +chives +chocolate +choice +choir +choker +cholesterol +choosing +chop +chops +chopstick +chopsticks +chord +chorus +chow +chowder +chrome +chromolithograph +chronicle +chronograph +chronometer +chrysalis +chub +chuck +chug +church +churn +chutney +cicada +cigarette +cilantro +cinder +cinema +cinnamon +circadian +circle +circuit +circulation +circumference +circumstance +cirrhosis +cirrus +citizen +citizenship +citron +citrus +city +civilian +civilisation +civilization +claim +clam +clamp +clan +clank +clapboard +clarification +clarinet +clarity +clasp +class +classic +classification +classmate +classroom +clause +clave +clavicle +clavier +claw +clay +cleaner +clearance +clearing +cleat +cleavage +clef +cleft +clergyman +cleric +clerk +click +client +cliff +climate +climb +clinic +clip +clipboard +clipper +cloak +cloakroom +clock +clockwork +clogs +cloister +clone +close +closet +closing +closure +cloth +clothes +clothing +cloud +cloudburst +clove +clover +cloves +club +clue +cluster +clutch +co-producer +coach +coal +coalition +coast +coaster +coat +cob +cobbler +cobweb +cock +cockpit +cockroach +cocktail +cocoa +coconut +cod +code +codepage +codling +codon +codpiece +coevolution +cofactor +coffee +coffin +cohesion +cohort +coil +coin +coincidence +coinsurance +coke +cold +coleslaw +coliseum +collaboration +collagen +collapse +collar +collard +collateral +colleague +collection +collectivisation +collectivization +collector +college +collision +colloquy +colon +colonial +colonialism +colonisation +colonization +colony +color +colorlessness +colt +column +columnist +comb +combat +combination +combine +comeback +comedy +comestible +comfort +comfortable +comic +comics +comma +command +commander +commandment +comment +commerce +commercial +commission +commitment +committee +commodity +common +commonsense +commotion +communicant +communication +communion +communist +community +commuter +company +comparison +compass +compassion +compassionate +compensation +competence +competition +competitor +complaint +complement +completion +complex +complexity +compliance +complication +complicity +compliment +component +comportment +composer +composite +composition +compost +comprehension +compress +compromise +comptroller +compulsion +computer +comradeship +con +concentrate +concentration +concept +conception +concern +concert +conclusion +concrete +condition +conditioner +condominium +condor +conduct +conductor +cone +confectionery +conference +confidence +confidentiality +configuration +confirmation +conflict +conformation +confusion +conga +congo +congregation +congress +congressman +congressperson +conifer +connection +connotation +conscience +consciousness +consensus +consent +consequence +conservation +conservative +consideration +consignment +consist +consistency +console +consonant +conspiracy +conspirator +constant +constellation +constitution +constraint +construction +consul +consulate +consulting +consumer +consumption +contact +contact lens +contagion +container +content +contention +contest +context +continent +contingency +continuity +contour +contract +contractor +contrail +contrary +contrast +contribution +contributor +control +controller +controversy +convection +convenience +convention +conversation +conversion +convert +convertible +conviction +cook +cookbook +cookie +cooking +coonskin +cooperation +coordination +coordinator +cop +cop-out +cope +copper +copy +copying +copyright +copywriter +coral +cord +corduroy +core +cork +cormorant +corn +corner +cornerstone +cornet +cornflakes +cornmeal +corporal +corporation +corporatism +corps +corral +correspondence +correspondent +corridor +corruption +corsage +cosset +cost +costume +cot +cottage +cotton +couch +cougar +cough +council +councilman +councilor +councilperson +counsel +counseling +counselling +counsellor +counselor +count +counter +counter-force +counterpart +counterterrorism +countess +country +countryside +county +couple +coupon +courage +course +court +courthouse +courtroom +cousin +covariate +cover +coverage +coverall +cow +cowbell +cowboy +coyote +crab +crack +cracker +crackers +cradle +craft +craftsman +cranberry +crane +cranky +crap +crash +crate +cravat +craw +crawdad +crayfish +crayon +crazy +cream +creation +creationism +creationist +creative +creativity +creator +creature +creche +credential +credenza +credibility +credit +creditor +creek +creme brulee +crepe +crest +crew +crewman +crewmate +crewmember +crewmen +cria +crib +cribbage +cricket +cricketer +crime +criminal +crinoline +crisis +crisp +criteria +criterion +critic +criticism +crocodile +crocus +croissant +crook +crop +cross +cross-contamination +cross-stitch +crotch +croup +crow +crowd +crown +crucifixion +crude +cruelty +cruise +crumb +crunch +crusader +crush +crust +cry +crystal +crystallography +cub +cube +cuckoo +cucumber +cue +cuff-link +cuisine +cultivar +cultivator +culture +culvert +cummerbund +cup +cupboard +cupcake +cupola +curd +cure +curio +curiosity +curl +curler +currant +currency +current +curriculum +curry +curse +cursor +curtailment +curtain +curve +cushion +custard +custody +custom +customer +cut +cuticle +cutlet +cutover +cutting +cyclamen +cycle +cyclone +cyclooxygenase +cygnet +cylinder +cymbal +cynic +cyst +cytokine +cytoplasm +dad +daddy +daffodil +dagger +dahlia +daikon +daily +dairy +daisy +dam +damage +dame +damn +dance +dancer +dancing +dandelion +danger +dare +dark +darkness +darn +dart +dash +dashboard +data +database +date +daughter +dawn +day +daybed +daylight +dead +deadline +deal +dealer +dealing +dearest +death +deathwatch +debate +debris +debt +debtor +decade +decadence +decency +decimal +decision +decision-making +deck +declaration +declination +decline +decoder +decongestant +decoration +decrease +decryption +dedication +deduce +deduction +deed +deep +deer +default +defeat +defendant +defender +defense +deficit +definition +deformation +degradation +degree +delay +deliberation +delight +delivery +demand +democracy +democrat +demon +demur +den +denim +denominator +density +dentist +deodorant +department +departure +dependency +dependent +deployment +deposit +deposition +depot +depression +depressive +depth +deputy +derby +derivation +derivative +derrick +descendant +descent +description +desert +design +designation +designer +desire +desk +desktop +dessert +destination +destiny +destroyer +destruction +detail +detainee +detainment +detection +detective +detector +detention +determination +detour +devastation +developer +developing +development +developmental +deviance +deviation +device +devil +dew +dhow +diabetes +diadem +diagnosis +diagram +dial +dialect +dialogue +diam +diamond +diaper +diaphragm +diarist +diary +dibble +dick +dickey +dictaphone +dictator +diction +dictionary +die +diesel +diet +difference +differential +difficulty +diffuse +dig +digestion +digestive +digger +digging +digit +dignity +dilapidation +dill +dilution +dime +dimension +dimple +diner +dinghy +dining +dinner +dinosaur +dioxide +dip +diploma +diplomacy +dipstick +direction +directive +director +directory +dirndl +dirt +disability +disadvantage +disagreement +disappointment +disarmament +disaster +discharge +discipline +disclaimer +disclosure +disco +disconnection +discount +discourse +discovery +discrepancy +discretion +discrimination +discussion +disdain +disease +disembodiment +disengagement +disguise +disgust +dish +dishwasher +disk +disparity +dispatch +displacement +display +disposal +disposer +disposition +dispute +disregard +disruption +dissemination +dissonance +distance +distinction +distortion +distribution +distributor +district +divalent +divan +diver +diversity +divide +dividend +divider +divine +diving +division +divorce +doc +dock +doctor +doctorate +doctrine +document +documentary +documentation +doe +dog +doggie +dogsled +dogwood +doing +doll +dollar +dollop +dolman +dolor +dolphin +domain +dome +domination +donation +donkey +donor +donut +door +doorbell +doorknob +doorpost +doorway +dory +dose +dot +double +doubling +doubt +doubter +dough +doughnut +down +downfall +downforce +downgrade +download +downstairs +downtown +downturn +dozen +draft +drag +dragon +dragonfly +dragonfruit +dragster +drain +drainage +drake +drama +dramaturge +drapes +draw +drawbridge +drawer +drawing +dream +dreamer +dredger +dress +dresser +dressing +drill +drink +drinking +drive +driver +driveway +driving +drizzle +dromedary +drop +drudgery +drug +drum +drummer +drunk +dryer +duck +duckling +dud +dude +due +duel +dueling +duffel +dugout +dulcimer +dumbwaiter +dump +dump truck +dune +dune buggy +dungarees +dungeon +duplexer +duration +durian +dusk +dust +dust storm +duster +duty +dwarf +dwell +dwelling +dynamics +dynamite +dynamo +dynasty +dysfunction +e-book +e-mail +e-reader +eagle +eaglet +ear +eardrum +earmuffs +earnings +earplug +earring +earrings +earth +earthquake +earthworm +ease +easel +east +eating +eaves +eavesdropper +ecclesia +echidna +eclipse +ecliptic +ecology +economics +economy +ecosystem +ectoderm +ectodermal +ecumenist +eddy +edge +edger +edible +editing +edition +editor +editorial +education +eel +effacement +effect +effective +effectiveness +effector +efficacy +efficiency +effort +egg +egghead +eggnog +eggplant +ego +eicosanoid +ejector +elbow +elderberry +election +electricity +electrocardiogram +electronics +element +elephant +elevation +elevator +eleventh +elf +elicit +eligibility +elimination +elite +elixir +elk +ellipse +elm +elongation +elver +email +emanate +embarrassment +embassy +embellishment +embossing +embryo +emerald +emergence +emergency +emergent +emery +emission +emitter +emotion +emphasis +empire +employ +employee +employer +employment +empowerment +emu +enactment +encirclement +enclave +enclosure +encounter +encouragement +encyclopedia +end +endive +endoderm +endorsement +endothelium +endpoint +enemy +energy +enforcement +engagement +engine +engineer +engineering +enigma +enjoyment +enquiry +enrollment +enterprise +entertainment +enthusiasm +entirety +entity +entrance +entree +entrepreneur +entry +envelope +environment +envy +enzyme +epauliere +epee +ephemera +ephemeris +ephyra +epic +episode +epithelium +epoch +eponym +epoxy +equal +equality +equation +equinox +equipment +equity +equivalent +era +eraser +erection +erosion +error +escalator +escape +escort +espadrille +espalier +essay +essence +essential +establishment +estate +estimate +estrogen +estuary +eternity +ethernet +ethics +ethnicity +ethyl +euphonium +eurocentrism +evaluation +evaluator +evaporation +eve +evening +evening-wear +event +everybody +everyone +everything +eviction +evidence +evil +evocation +evolution +ex-husband +ex-wife +exaggeration +exam +examination +examiner +example +exasperation +excellence +exception +excerpt +excess +exchange +excitement +exclamation +excursion +excuse +execution +executive +executor +exercise +exhaust +exhaustion +exhibit +exhibition +exile +existence +exit +exocrine +expansion +expansionism +expectancy +expectation +expedition +expense +experience +experiment +experimentation +expert +expertise +explanation +exploration +explorer +explosion +export +expose +exposition +exposure +expression +extension +extent +exterior +external +extinction +extreme +extremist +eye +eyeball +eyebrow +eyebrows +eyeglasses +eyelash +eyelashes +eyelid +eyelids +eyeliner +eyestrain +eyrie +fabric +face +facelift +facet +facility +facsimile +fact +factor +factory +faculty +fahrenheit +fail +failure +fairness +fairy +faith +faithful +fall +fallacy +falling-out +fame +familiar +familiarity +family +fan +fang +fanlight +fanny +fanny-pack +fantasy +farm +farmer +farming +farmland +farrow +fascia +fashion +fat +fate +father +father-in-law +fatigue +fatigues +faucet +fault +fav +fava +favor +favorite +fawn +fax +fear +feast +feather +feature +fedelini +federation +fedora +fee +feed +feedback +feeding +feel +feeling +fellow +felony +female +fen +fence +fencing +fender +feng +fennel +ferret +ferry +ferryboat +fertilizer +festival +fetus +few +fiber +fiberglass +fibre +fibroblast +fibrosis +ficlet +fiction +fiddle +field +fiery +fiesta +fifth +fig +fight +fighter +figure +figurine +file +filing +fill +fillet +filly +film +filter +filth +final +finance +financing +finding +fine +finer +finger +fingerling +fingernail +finish +finisher +fir +fire +fireman +fireplace +firewall +firm +first +fish +fishbone +fisherman +fishery +fishing +fishmonger +fishnet +fisting +fit +fitness +fix +fixture +flag +flair +flame +flan +flanker +flare +flash +flat +flatboat +flavor +flax +fleck +fledgling +fleece +flesh +flexibility +flick +flicker +flight +flint +flintlock +flip-flops +flock +flood +floodplain +floor +floozie +flour +flow +flower +flu +flugelhorn +fluke +flume +flung +flute +fly +flytrap +foal +foam +fob +focus +fog +fold +folder +folk +folklore +follower +following +fondue +font +food +foodstuffs +fool +foot +footage +football +footnote +footprint +footrest +footstep +footstool +footwear +forage +forager +foray +force +ford +forearm +forebear +forecast +forehead +foreigner +forelimb +forest +forestry +forever +forgery +fork +form +formal +formamide +format +formation +former +formicarium +formula +fort +forte +fortnight +fortress +fortune +forum +foundation +founder +founding +fountain +fourths +fowl +fox +foxglove +fraction +fragrance +frame +framework +fratricide +fraud +fraudster +freak +freckle +freedom +freelance +freezer +freezing +freight +freighter +frenzy +freon +frequency +fresco +friction +fridge +friend +friendship +fries +frigate +fright +fringe +fritter +frock +frog +front +frontier +frost +frosting +frown +fruit +frustration +fry +fuck +fuel +fugato +fulfillment +full +fun +function +functionality +fund +funding +fundraising +funeral +fur +furnace +furniture +furry +fusarium +futon +future +gadget +gaffe +gaffer +gain +gaiters +gale +gall-bladder +gallery +galley +gallon +galoshes +gambling +game +gamebird +gaming +gamma-ray +gander +gang +gap +garage +garb +garbage +garden +garlic +garment +garter +gas +gasket +gasoline +gasp +gastronomy +gastropod +gate +gateway +gather +gathering +gator +gauge +gauntlet +gavel +gazebo +gazelle +gear +gearshift +geek +gel +gelatin +gelding +gem +gemsbok +gender +gene +general +generation +generator +generosity +genetics +genie +genius +genocide +genre +gentleman +geography +geology +geometry +geranium +gerbil +gesture +geyser +gherkin +ghost +giant +gift +gig +gigantism +giggle +ginger +gingerbread +ginseng +giraffe +girdle +girl +girlfriend +git +glacier +gladiolus +glance +gland +glass +glasses +glee +glen +glider +gliding +glimpse +globe +glockenspiel +gloom +glory +glove +glow +glucose +glue +glut +glutamate +gnat +gnu +go-kart +goal +goat +gobbler +god +goddess +godfather +godmother +godparent +goggles +going +gold +goldfish +golf +gondola +gong +good +good-bye +goodbye +goodie +goodness +goodnight +goodwill +goose +gopher +gorilla +gosling +gossip +governance +government +governor +gown +grab-bag +grace +grade +gradient +graduate +graduation +graffiti +graft +grain +gram +grammar +gran +grand +grandchild +granddaughter +grandfather +grandma +grandmom +grandmother +grandpa +grandparent +grandson +granny +granola +grant +grape +grapefruit +graph +graphic +grasp +grass +grasshopper +grassland +gratitude +gravel +gravitas +gravity +gravy +gray +grease +great-grandfather +great-grandmother +greatness +greed +green +greenhouse +greens +grenade +grey +grid +grief +grill +grin +grip +gripper +grit +grocery +ground +group +grouper +grouse +grove +growth +grub +guacamole +guarantee +guard +guava +guerrilla +guess +guest +guestbook +guidance +guide +guideline +guilder +guilt +guilty +guinea +guitar +guitarist +gum +gumshoe +gun +gunpowder +gutter +guy +gym +gymnast +gymnastics +gynaecology +gyro +habit +habitat +hacienda +hacksaw +hackwork +hail +hair +haircut +hake +half +half-brother +half-sister +halibut +hall +halloween +hallway +halt +ham +hamburger +hammer +hammock +hamster +hand +hand-holding +handball +handful +handgun +handicap +handle +handlebar +handmaiden +handover +handrail +handsaw +hanger +happening +happiness +harald +harbor +harbour +hard-hat +hardboard +hardcover +hardening +hardhat +hardship +hardware +hare +harm +harmonica +harmonise +harmonize +harmony +harp +harpooner +harpsichord +harvest +harvester +hash +hashtag +hassock +haste +hat +hatbox +hatchet +hatchling +hate +hatred +haunt +haven +haversack +havoc +hawk +hay +haze +hazel +hazelnut +head +headache +headlight +headline +headphones +headquarters +headrest +health +health-care +hearing +hearsay +heart +heart-throb +heartache +heartbeat +hearth +hearthside +heartwood +heat +heater +heating +heaven +heavy +hectare +hedge +hedgehog +heel +heifer +height +heir +heirloom +helicopter +helium +hell +hellcat +hello +helmet +helo +help +hemisphere +hemp +hen +hepatitis +herb +herbs +heritage +hermit +hero +heroine +heron +herring +hesitation +heterosexual +hexagon +heyday +hiccups +hide +hierarchy +high +high-rise +highland +highlight +highway +hike +hiking +hill +hint +hip +hippodrome +hippopotamus +hire +hiring +historian +history +hit +hive +hobbit +hobby +hockey +hoe +hog +hold +holder +hole +holiday +home +homeland +homeownership +hometown +homework +homicide +homogenate +homonym +homosexual +homosexuality +honesty +honey +honeybee +honeydew +honor +honoree +hood +hoof +hook +hop +hope +hops +horde +horizon +hormone +horn +hornet +horror +horse +horseradish +horst +hose +hosiery +hospice +hospital +hospitalisation +hospitality +hospitalization +host +hostel +hostess +hotdog +hotel +hound +hour +hourglass +house +houseboat +household +housewife +housework +housing +hovel +hovercraft +howard +howitzer +hub +hubcap +hubris +hug +hugger +hull +human +humanity +humidity +hummus +humor +humour +hunchback +hundred +hunger +hunt +hunter +hunting +hurdle +hurdler +hurricane +hurry +hurt +husband +hut +hutch +hyacinth +hybridisation +hybridization +hydrant +hydraulics +hydrocarb +hydrocarbon +hydrofoil +hydrogen +hydrolyse +hydrolysis +hydrolyze +hydroxyl +hyena +hygienic +hype +hyphenation +hypochondria +hypothermia +hypothesis +ice +ice-cream +iceberg +icebreaker +icecream +icicle +icing +icon +icy +id +idea +ideal +identification +identity +ideology +idiom +idiot +igloo +ignorance +ignorant +ikebana +illegal +illiteracy +illness +illusion +illustration +image +imagination +imbalance +imitation +immigrant +immigration +immortal +impact +impairment +impala +impediment +implement +implementation +implication +import +importance +impostor +impress +impression +imprisonment +impropriety +improvement +impudence +impulse +in-joke +in-laws +inability +inauguration +inbox +incandescence +incarnation +incense +incentive +inch +incidence +incident +incision +inclusion +income +incompetence +inconvenience +increase +incubation +independence +independent +index +indication +indicator +indigence +individual +industrialisation +industrialization +industry +inequality +inevitable +infancy +infant +infarction +infection +infiltration +infinite +infix +inflammation +inflation +influence +influx +info +information +infrastructure +infusion +inglenook +ingrate +ingredient +inhabitant +inheritance +inhibition +inhibitor +initial +initialise +initialize +initiative +injunction +injury +injustice +ink +inlay +inn +innervation +innocence +innocent +innovation +input +inquiry +inscription +insect +insectarium +insert +inside +insight +insolence +insomnia +inspection +inspector +inspiration +installation +instance +instant +instinct +institute +institution +instruction +instructor +instrument +instrumentalist +instrumentation +insulation +insurance +insurgence +insurrection +integer +integral +integration +integrity +intellect +intelligence +intensity +intent +intention +intentionality +interaction +interchange +interconnection +intercourse +interest +interface +interferometer +interior +interject +interloper +internet +interpretation +interpreter +interval +intervenor +intervention +interview +interviewer +intestine +introduction +intuition +invader +invasion +invention +inventor +inventory +inverse +inversion +investigation +investigator +investment +investor +invitation +invite +invoice +involvement +iridescence +iris +iron +ironclad +irony +irrigation +ischemia +island +isogloss +isolation +issue +item +itinerary +ivory +jack +jackal +jacket +jackfruit +jade +jaguar +jail +jailhouse +jalapeño +jam +jar +jasmine +jaw +jazz +jealousy +jeans +jeep +jelly +jellybeans +jellyfish +jerk +jet +jewel +jeweller +jewellery +jewelry +jicama +jiffy +job +jockey +jodhpurs +joey +jogging +joint +joke +jot +journal +journalism +journalist +journey +joy +judge +judgment +judo +jug +juggernaut +juice +julienne +jumbo +jump +jumper +jumpsuit +jungle +junior +junk +junker +junket +jury +justice +justification +jute +kale +kamikaze +kangaroo +karate +kayak +kazoo +kebab +keep +keeper +kendo +kennel +ketch +ketchup +kettle +kettledrum +key +keyboard +keyboarding +keystone +kick +kick-off +kid +kidney +kielbasa +kill +killer +killing +kilogram +kilometer +kilt +kimono +kinase +kind +kindness +king +kingdom +kingfish +kiosk +kiss +kit +kitchen +kite +kitsch +kitten +kitty +kiwi +knee +kneejerk +knickers +knife +knife-edge +knight +knitting +knock +knot +know-how +knowledge +knuckle +koala +kohlrabi +kumquat +lab +label +labor +laboratory +laborer +labour +labourer +lace +lack +lacquerware +lad +ladder +ladle +lady +ladybug +lag +lake +lamb +lambkin +lament +lamp +lanai +land +landform +landing +landmine +landscape +lane +language +lantern +lap +laparoscope +lapdog +laptop +larch +lard +larder +lark +larva +laryngitis +lasagna +lashes +last +latency +latex +lathe +latitude +latte +latter +laugh +laughter +laundry +lava +law +lawmaker +lawn +lawsuit +lawyer +lay +layer +layout +lead +leader +leadership +leading +leaf +league +leaker +leap +learning +leash +leather +leave +leaver +lecture +leek +leeway +left +leg +legacy +legal +legend +legging +legislation +legislator +legislature +legitimacy +legume +leisure +lemon +lemonade +lemur +lender +lending +length +lens +lentil +leopard +leprosy +leptocephalus +lesbian +lesson +letter +lettuce +level +lever +leverage +leveret +liability +liar +liberty +libido +library +licence +license +licensing +licorice +lid +lie +lieu +lieutenant +life +lifestyle +lifetime +lift +ligand +light +lighting +lightning +lightscreen +ligula +likelihood +likeness +lilac +lily +limb +lime +limestone +limit +limitation +limo +line +linen +liner +linguist +linguistics +lining +link +linkage +linseed +lion +lip +lipid +lipoprotein +lipstick +liquid +liquidity +liquor +list +listening +listing +literate +literature +litigation +litmus +litter +littleneck +liver +livestock +living +lizard +llama +load +loading +loaf +loafer +loan +lobby +lobotomy +lobster +local +locality +location +lock +locker +locket +locomotive +locust +lode +loft +log +loggia +logic +login +logistics +logo +loincloth +lollipop +loneliness +longboat +longitude +look +lookout +loop +loophole +loquat +lord +loss +lot +lotion +lottery +lounge +louse +lout +love +lover +lox +loyalty +luck +luggage +lumber +lumberman +lunch +luncheonette +lunchmeat +lunchroom +lung +lunge +lust +lute +luxury +lychee +lycra +lye +lymphocyte +lynx +lyocell +lyre +lyrics +lysine +mRNA +macadamia +macaroni +macaroon +macaw +machine +machinery +macrame +macro +macrofauna +madam +maelstrom +maestro +magazine +maggot +magic +magnet +magnitude +maid +maiden +mail +mailbox +mailer +mailing +mailman +main +mainland +mainstream +maintainer +maintenance +maize +major +major-league +majority +makeover +maker +makeup +making +male +malice +mall +mallard +mallet +malnutrition +mama +mambo +mammoth +man +manacle +management +manager +manatee +mandarin +mandate +mandolin +mangle +mango +mangrove +manhunt +maniac +manicure +manifestation +manipulation +mankind +manner +manor +mansard +manservant +mansion +mantel +mantle +mantua +manufacturer +manufacturing +many +map +maple +mapping +maracas +marathon +marble +march +mare +margarine +margin +mariachi +marimba +marines +marionberry +mark +marker +market +marketer +marketing +marketplace +marksman +markup +marmalade +marriage +marsh +marshland +marshmallow +marten +marxism +mascara +mask +masonry +mass +massage +mast +master +masterpiece +mastication +mastoid +mat +match +matchmaker +mate +material +maternity +math +mathematics +matrix +matter +mattock +mattress +max +maximum +maybe +mayonnaise +mayor +meadow +meal +mean +meander +meaning +means +meantime +measles +measure +measurement +meat +meatball +meatloaf +mecca +mechanic +mechanism +med +medal +media +median +medication +medicine +medium +meet +meeting +melatonin +melody +melon +member +membership +membrane +meme +memo +memorial +memory +men +menopause +menorah +mention +mentor +menu +merchandise +merchant +mercury +meridian +meringue +merit +mesenchyme +mess +message +messenger +messy +metabolite +metal +metallurgist +metaphor +meteor +meteorology +meter +methane +method +methodology +metric +metro +metronome +mezzanine +microlending +micronutrient +microphone +microwave +mid-course +midden +middle +middleman +midline +midnight +midwife +might +migrant +migration +mile +mileage +milepost +milestone +military +milk +milkshake +mill +millennium +millet +millimeter +million +millisecond +millstone +mime +mimosa +min +mincemeat +mind +mine +mineral +mineshaft +mini +mini-skirt +minibus +minimalism +minimum +mining +minion +minister +mink +minnow +minor +minor-league +minority +mint +minute +miracle +mirror +miscarriage +miscommunication +misfit +misnomer +misogyny +misplacement +misreading +misrepresentation +miss +missile +mission +missionary +mist +mistake +mister +misunderstand +miter +mitten +mix +mixer +mixture +moai +moat +mob +mobile +mobility +mobster +moccasins +mocha +mochi +mode +model +modeling +modem +modernist +modernity +modification +molar +molasses +molding +mole +molecule +mom +moment +monastery +monasticism +money +monger +monitor +monitoring +monk +monkey +monocle +monopoly +monotheism +monsoon +monster +month +monument +mood +moody +moon +moonlight +moonscape +moonshine +moose +mop +morale +morbid +morbidity +morning +moron +morphology +morsel +mortal +mortality +mortgage +mortise +mosque +mosquito +most +motel +moth +mother +mother-in-law +motion +motivation +motive +motor +motorboat +motorcar +motorcycle +mound +mountain +mouse +mouser +mousse +moustache +mouth +mouton +movement +mover +movie +mower +mozzarella +mud +muffin +mug +mukluk +mule +multimedia +murder +muscat +muscatel +muscle +musculature +museum +mushroom +music +music-box +music-making +musician +muskrat +mussel +mustache +mustard +mutation +mutt +mutton +mycoplasma +mystery +myth +mythology +nail +name +naming +nanoparticle +napkin +narrative +nasal +nation +nationality +native +naturalisation +nature +navigation +necessity +neck +necklace +necktie +nectar +nectarine +need +needle +neglect +negligee +negotiation +neighbor +neighborhood +neighbour +neighbourhood +neologism +neon +neonate +nephew +nerve +nest +nestling +nestmate +net +netball +netbook +netsuke +network +networking +neurobiologist +neuron +neuropathologist +neuropsychiatry +news +newsletter +newspaper +newsprint +newsstand +nexus +nibble +nicety +niche +nick +nickel +nickname +niece +night +nightclub +nightgown +nightingale +nightlife +nightlight +nightmare +ninja +nit +nitrogen +nobody +nod +node +noir +noise +nonbeliever +nonconformist +nondisclosure +nonsense +noodle +noodles +noon +norm +normal +normalisation +normalization +north +nose +notation +note +notebook +notepad +nothing +notice +notion +notoriety +nougat +noun +nourishment +novel +nucleotidase +nucleotide +nudge +nuke +number +numeracy +numeric +numismatist +nun +nurse +nursery +nursing +nurture +nut +nutmeg +nutrient +nutrition +nylon +nymph +oak +oar +oasis +oat +oatmeal +oats +obedience +obesity +obi +object +objection +objective +obligation +oboe +observation +observatory +obsession +obsidian +obstacle +occasion +occupation +occurrence +ocean +ocelot +octagon +octave +octavo +octet +octopus +odometer +odyssey +oeuvre +off-ramp +offence +offense +offer +offering +office +officer +official +offset +oil +okra +oldie +oleo +olive +omega +omelet +omission +omnivore +oncology +onion +online +onset +opening +opera +operating +operation +operator +ophthalmologist +opinion +opium +opossum +opponent +opportunist +opportunity +opposite +opposition +optimal +optimisation +optimist +optimization +option +orange +orangutan +orator +orchard +orchestra +orchid +order +ordinary +ordination +ore +oregano +organ +organisation +organising +organization +organizing +orient +orientation +origin +original +originality +ornament +osmosis +osprey +ostrich +other +otter +ottoman +ounce +outback +outcome +outfielder +outfit +outhouse +outlaw +outlay +outlet +outline +outlook +output +outrage +outrigger +outrun +outset +outside +oval +ovary +oven +overcharge +overclocking +overcoat +overexertion +overflight +overhead +overheard +overload +overnighter +overshoot +oversight +overview +overweight +owl +owner +ownership +ox +oxford +oxygen +oyster +ozone +pace +pacemaker +pack +package +packaging +packet +pad +paddle +paddock +pagan +page +pagoda +pail +pain +paint +painter +painting +paintwork +pair +pajamas +palace +palate +palm +pamphlet +pan +pancake +pancreas +panda +panel +panic +pannier +panpipe +pansy +panther +panties +pantologist +pantology +pantry +pants +pantsuit +panty +pantyhose +papa +papaya +paper +paperback +paperwork +parable +parachute +parade +paradise +paragraph +parallelogram +paramecium +paramedic +parameter +paranoia +parcel +parchment +pard +pardon +parent +parenthesis +parenting +park +parka +parking +parliament +parole +parrot +parser +parsley +parsnip +part +participant +participation +particle +particular +partner +partnership +partridge +party +pass +passage +passbook +passenger +passing +passion +passive +passport +password +past +pasta +paste +pastor +pastoralist +pastry +pasture +pat +patch +pate +patent +patentee +path +pathogenesis +pathology +pathway +patience +patient +patina +patio +patriarch +patrimony +patriot +patrol +patroller +patrolling +patron +pattern +patty +pattypan +pause +pavement +pavilion +paw +pawnshop +pay +payee +payment +payoff +pea +peace +peach +peacoat +peacock +peak +peanut +pear +pearl +peasant +pecan +pecker +pedal +peek +peen +peer +peer-to-peer +pegboard +pelican +pelt +pen +penalty +pence +pencil +pendant +pendulum +penguin +penicillin +peninsula +penis +pennant +penny +pension +pentagon +peony +people +pepper +pepperoni +percent +percentage +perception +perch +perennial +perfection +performance +perfume +period +periodical +peripheral +permafrost +permission +permit +perp +perpendicular +persimmon +person +personal +personality +personnel +perspective +pest +pet +petal +petition +petitioner +petticoat +pew +pharmacist +pharmacopoeia +phase +pheasant +phenomenon +phenotype +pheromone +philanthropy +philosopher +philosophy +phone +phosphate +photo +photodiode +photograph +photographer +photography +photoreceptor +phrase +phrasing +physical +physics +physiology +pianist +piano +piccolo +pick +pickax +pickaxe +picket +pickle +pickup +picnic +picture +picturesque +pie +piece +pier +piety +pig +pigeon +piglet +pigpen +pigsty +pike +pilaf +pile +pilgrim +pilgrimage +pill +pillar +pillbox +pillow +pilot +pimp +pimple +pin +pinafore +pince-nez +pine +pineapple +pinecone +ping +pink +pinkie +pinot +pinstripe +pint +pinto +pinworm +pioneer +pipe +pipeline +piracy +pirate +piss +pistol +pit +pita +pitch +pitcher +pitching +pith +pizza +place +placebo +placement +placode +plagiarism +plain +plaintiff +plan +plane +planet +planning +plant +plantation +planter +planula +plaster +plasterboard +plastic +plate +platelet +platform +platinum +platter +platypus +play +player +playground +playroom +playwright +plea +pleasure +pleat +pledge +plenty +plier +pliers +plight +plot +plough +plover +plow +plowman +plug +plugin +plum +plumber +plume +plunger +plywood +pneumonia +pocket +pocket-watch +pocketbook +pod +podcast +poem +poet +poetry +poignance +point +poison +poisoning +poker +polarisation +polarization +pole +polenta +police +policeman +policy +polish +politician +politics +poll +polliwog +pollutant +pollution +polo +polyester +polyp +pomegranate +pomelo +pompom +poncho +pond +pony +pool +poor +pop +popcorn +poppy +popsicle +popularity +population +populist +porcelain +porch +porcupine +pork +porpoise +port +porter +portfolio +porthole +portion +portrait +position +possession +possibility +possible +post +postage +postbox +poster +posterior +postfix +pot +potato +potential +pottery +potty +pouch +poultry +pound +pounding +poverty +powder +power +practice +practitioner +prairie +praise +pray +prayer +precedence +precedent +precipitation +precision +predecessor +preface +preference +prefix +pregnancy +prejudice +prelude +premeditation +premier +premise +premium +preoccupation +preparation +prescription +presence +present +presentation +preservation +preserves +presidency +president +press +pressroom +pressure +pressurisation +pressurization +prestige +presume +pretzel +prevalence +prevention +prey +price +pricing +pride +priest +priesthood +primary +primate +prince +princess +principal +principle +print +printer +printing +prior +priority +prison +prisoner +privacy +private +privilege +prize +prizefight +probability +probation +probe +problem +procedure +proceedings +process +processing +processor +proctor +procurement +produce +producer +product +production +productivity +profession +professional +professor +profile +profit +progenitor +program +programme +programming +progress +progression +prohibition +project +proliferation +promenade +promise +promotion +prompt +pronoun +pronunciation +proof +proof-reader +propaganda +propane +property +prophet +proponent +proportion +proposal +proposition +proprietor +prose +prosecution +prosecutor +prospect +prosperity +prostacyclin +prostanoid +prostrate +protection +protein +protest +protocol +providence +provider +province +provision +prow +proximal +proximity +prune +pruner +pseudocode +pseudoscience +psychiatrist +psychoanalyst +psychologist +psychology +ptarmigan +pub +public +publication +publicity +publisher +publishing +pudding +puddle +puffin +pug +puggle +pulley +pulse +puma +pump +pumpernickel +pumpkin +pumpkinseed +pun +punch +punctuation +punishment +pup +pupa +pupil +puppet +puppy +purchase +puritan +purity +purple +purpose +purr +purse +pursuit +push +pusher +put +puzzle +pyramid +pyridine +quadrant +quail +qualification +quality +quantity +quart +quarter +quartet +quartz +queen +query +quest +question +questioner +questionnaire +quiche +quicksand +quiet +quill +quilt +quince +quinoa +quit +quiver +quota +quotation +quote +rabbi +rabbit +raccoon +race +racer +racing +racism +racist +rack +radar +radiator +radio +radiosonde +radish +raffle +raft +rag +rage +raid +rail +railing +railroad +railway +raiment +rain +rainbow +raincoat +rainmaker +rainstorm +rainy +raise +raisin +rake +rally +ram +rambler +ramen +ramie +ranch +rancher +randomisation +randomization +range +ranger +rank +rap +rape +raspberry +rat +rate +ratepayer +rating +ratio +rationale +rations +raven +ravioli +rawhide +ray +rayon +razor +reach +reactant +reaction +read +reader +readiness +reading +real +reality +realization +realm +reamer +rear +reason +reasoning +rebel +rebellion +reboot +recall +recapitulation +receipt +receiver +reception +receptor +recess +recession +recipe +recipient +reciprocity +reclamation +recliner +recognition +recollection +recommendation +reconsideration +record +recorder +recording +recovery +recreation +recruit +rectangle +red +redesign +redhead +redirect +rediscovery +reduction +reef +refectory +reference +referendum +reflection +reform +refreshments +refrigerator +refuge +refund +refusal +refuse +regard +regime +region +regionalism +register +registration +registry +regret +regulation +regulator +rehospitalisation +rehospitalization +reindeer +reinscription +reject +relation +relationship +relative +relaxation +relay +release +reliability +relief +religion +relish +reluctance +remains +remark +reminder +remnant +remote +removal +renaissance +rent +reorganisation +reorganization +repair +reparation +repayment +repeat +replacement +replica +replication +reply +report +reporter +reporting +repository +representation +representative +reprocessing +republic +republican +reputation +request +requirement +resale +rescue +research +researcher +resemblance +reservation +reserve +reservoir +reset +residence +resident +residue +resist +resistance +resolution +resolve +resort +resource +respect +respite +response +responsibility +rest +restaurant +restoration +restriction +restroom +restructuring +result +resume +retailer +retention +rethinking +retina +retirement +retouching +retreat +retrospect +retrospective +retrospectivity +return +reunion +revascularisation +revascularization +reveal +revelation +revenant +revenge +revenue +reversal +reverse +review +revitalisation +revitalization +revival +revolution +revolver +reward +rhetoric +rheumatism +rhinoceros +rhubarb +rhyme +rhythm +rib +ribbon +rice +riddle +ride +rider +ridge +riding +rifle +right +rim +ring +ringworm +riot +rip +ripple +rise +riser +risk +rite +ritual +river +riverbed +rivulet +road +roadway +roar +roast +robe +robin +robot +robotics +rock +rocker +rocket +rocket-ship +rod +role +roll +roller +romaine +romance +roof +room +roommate +rooster +root +rope +rose +rosemary +roster +rostrum +rotation +round +roundabout +route +router +routine +row +rowboat +rowing +rubber +rubbish +rubric +ruby +ruckus +rudiment +ruffle +rug +rugby +ruin +rule +ruler +ruling +rum +rumor +run +runaway +runner +running +runway +rush +rust +rutabaga +rye +sabre +sac +sack +saddle +sadness +safari +safe +safeguard +safety +saffron +sage +sail +sailboat +sailing +sailor +saint +sake +salad +salami +salary +sale +salesman +salmon +salon +saloon +salsa +salt +salute +samovar +sampan +sample +samurai +sanction +sanctity +sanctuary +sand +sandal +sandbar +sandpaper +sandwich +sanity +sardine +sari +sarong +sash +satellite +satin +satire +satisfaction +sauce +saucer +sauerkraut +sausage +savage +savannah +saving +savings +savior +saviour +savory +saw +saxophone +scaffold +scale +scallion +scallops +scalp +scam +scanner +scarecrow +scarf +scarification +scenario +scene +scenery +scent +schedule +scheduling +schema +scheme +schizophrenic +schnitzel +scholar +scholarship +school +schoolhouse +schooner +science +scientist +scimitar +scissors +scooter +scope +score +scorn +scorpion +scotch +scout +scow +scrambled +scrap +scraper +scratch +screamer +screen +screening +screenwriting +screw +screw-up +screwdriver +scrim +scrip +script +scripture +scrutiny +sculpting +sculptural +sculpture +sea +seabass +seafood +seagull +seal +seaplane +search +seashore +seaside +season +seat +seaweed +second +secrecy +secret +secretariat +secretary +secretion +section +sectional +sector +security +sediment +seed +seeder +seeker +seep +segment +seizure +selection +self +self-confidence +self-control +self-esteem +seller +selling +semantics +semester +semicircle +semicolon +semiconductor +seminar +senate +senator +sender +senior +sense +sensibility +sensitive +sensitivity +sensor +sentence +sentencing +sentiment +sepal +separation +septicaemia +sequel +sequence +serial +series +sermon +serum +serval +servant +server +service +servitude +sesame +session +set +setback +setting +settlement +settler +severity +sewer +sex +sexuality +shack +shackle +shade +shadow +shadowbox +shakedown +shaker +shallot +shallows +shame +shampoo +shanty +shape +share +shareholder +shark +shaw +shawl +shear +shearling +sheath +shed +sheep +sheet +shelf +shell +shelter +sherbet +sherry +shield +shift +shin +shine +shingle +ship +shipper +shipping +shipyard +shirt +shirtdress +shit +shoat +shock +shoe +shoe-horn +shoehorn +shoelace +shoemaker +shoes +shoestring +shofar +shoot +shootdown +shop +shopper +shopping +shore +shoreline +short +shortage +shorts +shortwave +shot +shoulder +shout +shovel +show +show-stopper +shower +shred +shrimp +shrine +shutdown +sibling +sick +sickness +side +sideboard +sideburns +sidecar +sidestream +sidewalk +siding +siege +sigh +sight +sightseeing +sign +signal +signature +signet +significance +signify +signup +silence +silica +silicon +silk +silkworm +sill +silly +silo +silver +similarity +simple +simplicity +simplification +simvastatin +sin +singer +singing +singular +sink +sinuosity +sip +sir +sister +sister-in-law +sitar +site +situation +size +skate +skating +skean +skeleton +ski +skiing +skill +skin +skirt +skull +skullcap +skullduggery +skunk +sky +skylight +skyline +skyscraper +skywalk +slang +slapstick +slash +slate +slave +slavery +slaw +sled +sledge +sleep +sleepiness +sleeping +sleet +sleuth +slice +slide +slider +slime +slip +slipper +slippers +slope +slot +sloth +slump +smell +smelting +smile +smith +smock +smog +smoke +smoking +smolt +smuggling +snack +snail +snake +snakebite +snap +snarl +sneaker +sneakers +sneeze +sniffle +snob +snorer +snow +snowboarding +snowflake +snowman +snowmobiling +snowplow +snowstorm +snowsuit +snuck +snug +snuggle +soap +soccer +socialism +socialist +society +sociology +sock +socks +soda +sofa +softball +softdrink +softening +software +soil +soldier +sole +solicitation +solicitor +solidarity +solidity +soliloquy +solitaire +solution +solvency +sombrero +somebody +someone +someplace +somersault +something +somewhere +son +sonar +sonata +song +songbird +sonnet +soot +sophomore +soprano +sorbet +sorghum +sorrel +sorrow +sort +soul +soulmate +sound +soundness +soup +source +sourwood +sousaphone +south +southeast +souvenir +sovereignty +sow +soy +soybean +space +spacing +spade +spaghetti +span +spandex +spank +sparerib +spark +sparrow +spasm +spat +spatula +spawn +speaker +speakerphone +speaking +spear +spec +special +specialist +specialty +species +specification +spectacle +spectacles +spectrograph +spectrum +speculation +speech +speed +speedboat +spell +spelling +spelt +spending +sphere +sphynx +spice +spider +spiderling +spike +spill +spinach +spine +spiral +spirit +spiritual +spirituality +spit +spite +spleen +splendor +split +spokesman +spokeswoman +sponge +sponsor +sponsorship +spool +spoon +spork +sport +sportsman +spot +spotlight +spouse +sprag +sprat +spray +spread +spreadsheet +spree +spring +sprinkles +sprinter +sprout +spruce +spud +spume +spur +spy +spyglass +square +squash +squatter +squeegee +squid +squirrel +stab +stability +stable +stack +stacking +stadium +staff +stag +stage +stain +stair +staircase +stake +stalk +stall +stallion +stamen +stamina +stamp +stance +stand +standard +standardisation +standardization +standing +standoff +standpoint +star +starboard +start +starter +state +statement +statin +station +station-wagon +statistic +statistics +statue +status +statute +stay +steak +stealth +steam +steamroller +steel +steeple +stem +stench +stencil +step +step-aunt +step-brother +step-daughter +step-father +step-grandfather +step-grandmother +step-mother +step-sister +step-son +step-uncle +stepdaughter +stepmother +stepping-stone +stepson +stereo +stew +steward +stick +sticker +stiletto +still +stimulation +stimulus +sting +stinger +stir-fry +stitch +stitcher +stock +stock-in-trade +stockings +stole +stomach +stone +stonework +stool +stop +stopsign +stopwatch +storage +store +storey +storm +story +story-telling +storyboard +stot +stove +strait +strand +stranger +strap +strategy +straw +strawberry +strawman +stream +street +streetcar +strength +stress +stretch +strife +strike +string +strip +stripe +strobe +stroke +structure +strudel +struggle +stucco +stud +student +studio +study +stuff +stumbling +stump +stupidity +sturgeon +sty +style +styling +stylus +sub +subcomponent +subconscious +subcontractor +subexpression +subgroup +subject +submarine +submitter +subprime +subroutine +subscription +subsection +subset +subsidence +subsidiary +subsidy +substance +substitution +subtitle +suburb +subway +success +succotash +suck +sucker +suede +suet +suffocation +sugar +suggestion +suicide +suit +suitcase +suite +sulfur +sultan +sum +summary +summer +summit +sun +sunbeam +sunbonnet +sundae +sunday +sundial +sunflower +sunglasses +sunlamp +sunlight +sunrise +sunroom +sunset +sunshine +superiority +supermarket +supernatural +supervision +supervisor +supper +supplement +supplier +supply +support +supporter +suppression +supreme +surface +surfboard +surge +surgeon +surgery +surname +surplus +surprise +surround +surroundings +surrounds +survey +survival +survivor +sushi +suspect +suspenders +suspension +sustainment +sustenance +swallow +swamp +swan +swanling +swath +sweat +sweater +sweatshirt +sweatshop +sweatsuit +sweets +swell +swim +swimming +swimsuit +swine +swing +switch +switchboard +switching +swivel +sword +swordfight +swordfish +sycamore +symbol +symmetry +sympathy +symptom +syndicate +syndrome +synergy +synod +synonym +synthesis +syrup +system +t-shirt +tab +tabby +tabernacle +table +tablecloth +tablet +tabletop +tachometer +tackle +taco +tactics +tactile +tadpole +tag +tail +tailbud +tailor +tailspin +take-out +takeover +tale +talent +talk +talking +tam-o'-shanter +tamale +tambour +tambourine +tan +tandem +tangerine +tank +tank-top +tanker +tankful +tap +tape +tapioca +target +taro +tarragon +tart +task +tassel +taste +tatami +tattler +tattoo +tavern +tax +taxi +taxicab +taxpayer +tea +teacher +teaching +team +teammate +teapot +tear +tech +technician +technique +technologist +technology +tectonics +teen +teenager +teepee +telephone +telescreen +teletype +television +tell +teller +temp +temper +temperature +temple +tempo +temporariness +temporary +temptation +temptress +tenant +tendency +tender +tenement +tenet +tennis +tenor +tension +tensor +tent +tentacle +tenth +tepee +teriyaki +term +terminal +termination +terminology +termite +terrace +terracotta +terrapin +terrarium +territory +terror +terrorism +terrorist +test +testament +testimonial +testimony +testing +text +textbook +textual +texture +thanks +thaw +theater +theft +theism +theme +theology +theory +therapist +therapy +thermals +thermometer +thermostat +thesis +thickness +thief +thigh +thing +thinking +thirst +thistle +thong +thongs +thorn +thought +thousand +thread +threat +threshold +thrift +thrill +throat +throne +thrush +thrust +thug +thumb +thump +thunder +thunderbolt +thunderhead +thunderstorm +thyme +tiara +tic +tick +ticket +tide +tie +tiger +tights +tile +till +tilt +timbale +timber +time +timeline +timeout +timer +timetable +timing +timpani +tin +tinderbox +tinkle +tintype +tip +tire +tissue +titanium +title +toad +toast +toaster +tobacco +today +toe +toenail +toffee +tofu +tog +toga +toilet +tolerance +tolerant +toll +tom-tom +tomatillo +tomato +tomb +tomography +tomorrow +ton +tonality +tone +tongue +tonic +tonight +tool +toot +tooth +toothbrush +toothpaste +toothpick +top +top-hat +topic +topsail +toque +toreador +tornado +torso +torte +tortellini +tortilla +tortoise +tosser +total +tote +touch +tough-guy +tour +tourism +tourist +tournament +tow-truck +towel +tower +town +townhouse +township +toy +trace +trachoma +track +tracking +tracksuit +tract +tractor +trade +trader +trading +tradition +traditionalism +traffic +trafficker +tragedy +trail +trailer +trailpatrol +train +trainer +training +trait +tram +tramp +trance +transaction +transcript +transfer +transformation +transit +transition +translation +transmission +transom +transparency +transplantation +transport +transportation +trap +trapdoor +trapezium +trapezoid +trash +travel +traveler +tray +treasure +treasury +treat +treatment +treaty +tree +trek +trellis +tremor +trench +trend +triad +trial +triangle +tribe +tributary +trick +trigger +trigonometry +trillion +trim +trinket +trip +tripod +tritone +triumph +trolley +trombone +troop +trooper +trophy +trouble +trousers +trout +trove +trowel +truck +trumpet +trunk +trust +trustee +truth +try +tsunami +tub +tuba +tube +tuber +tug +tugboat +tuition +tulip +tumbler +tummy +tuna +tune +tune-up +tunic +tunnel +turban +turf +turkey +turmeric +turn +turning +turnip +turnover +turnstile +turret +turtle +tusk +tussle +tutu +tuxedo +tweet +tweezers +twig +twilight +twine +twins +twist +twister +twitter +type +typeface +typewriter +typhoon +ukulele +ultimatum +umbrella +unblinking +uncertainty +uncle +underclothes +underestimate +underground +underneath +underpants +underpass +undershirt +understanding +understatement +undertaker +underwear +underweight +underwire +underwriting +unemployment +unibody +uniform +uniformity +union +unique +unit +unity +universe +university +update +upgrade +uplift +upper +upstairs +upward +urge +urgency +urn +usage +use +user +usher +usual +utensil +utilisation +utility +utilization +vacation +vaccine +vacuum +vagrant +valance +valentine +validate +validity +valley +valuable +value +vampire +van +vanadyl +vane +vanilla +vanity +variability +variable +variant +variation +variety +vascular +vase +vault +vaulting +veal +vector +vegetable +vegetarian +vegetarianism +vegetation +vehicle +veil +vein +veldt +vellum +velocity +velodrome +velvet +vendor +veneer +vengeance +venison +venom +venti +venture +venue +veranda +verb +verdict +verification +vermicelli +vernacular +verse +version +vertigo +verve +vessel +vest +vestment +vet +veteran +veterinarian +veto +viability +vibe +vibraphone +vibration +vibrissae +vice +vicinity +victim +victory +video +view +viewer +vignette +villa +village +vine +vinegar +vineyard +vintage +vintner +vinyl +viola +violation +violence +violet +violin +virginal +virtue +virus +visa +viscose +vise +vision +visit +visitor +visor +vista +visual +vitality +vitamin +vitro +vivo +vixen +vodka +vogue +voice +void +vol +volatility +volcano +volleyball +volume +volunteer +volunteering +vomit +vote +voter +voting +voyage +vulture +wad +wafer +waffle +wage +wagon +waist +waistband +wait +waiter +waiting +waitress +waiver +wake +walk +walker +walking +walkway +wall +wallaby +wallet +walnut +walrus +wampum +wannabe +want +war +warden +wardrobe +warfare +warlock +warlord +warm-up +warming +warmth +warning +warrant +warren +warrior +wasabi +wash +washbasin +washcloth +washer +washtub +wasp +waste +wastebasket +wasting +watch +watcher +watchmaker +water +waterbed +watercress +waterfall +waterfront +watermelon +waterskiing +waterspout +waterwheel +wave +waveform +wax +way +weakness +wealth +weapon +wear +weasel +weather +web +webinar +webmail +webpage +website +wedding +wedge +weed +weeder +weedkiller +week +weekend +weekender +weight +weird +welcome +welfare +well +well-being +west +western +wet-bar +wetland +wetsuit +whack +whale +wharf +wheat +wheel +whelp +whey +whip +whirlpool +whirlwind +whisker +whiskey +whisper +whistle +white +whole +wholesale +wholesaler +whorl +wick +widget +widow +width +wife +wifi +wild +wildebeest +wilderness +wildlife +will +willingness +willow +win +wind +wind-chime +windage +window +windscreen +windshield +wine +winery +wing +wingman +wingtip +wink +winner +winter +wire +wiretap +wiring +wisdom +wiseguy +wish +wisteria +wit +witch +witch-hunt +withdrawal +witness +wok +wolf +woman +wombat +wonder +wont +wood +woodchuck +woodland +woodshed +woodwind +wool +woolens +word +wording +work +workbench +worker +workforce +workhorse +working +workout +workplace +workshop +world +worm +worry +worship +worshiper +worth +wound +wrap +wraparound +wrapper +wrapping +wreck +wrecker +wren +wrench +wrestler +wriggler +wrinkle +wrist +writer +writing +wrong +xylophone +yacht +yahoo +yak +yam +yang +yard +yarmulke +yarn +yawl +year +yeast +yellow +yellowjacket +yesterday +yew +yin +yoga +yogurt +yoke +yolk +young +youngster +yourself +youth +yoyo +yurt +zampone +zebra +zebrafish +zen +zephyr +zero +ziggurat +zinc +zipper +zither +zombie +zone +zoo +zoologist +zoology +zoot-suit +zucchini \ No newline at end of file diff --git a/Lambdas/Scraping/prefix_list_builder.py b/Lambdas/Scraping/prefix_list_builder.py new file mode 100644 index 0000000..d5bbe2b --- /dev/null +++ b/Lambdas/Scraping/prefix_list_builder.py @@ -0,0 +1,25 @@ +wordlist = [] +with open("nounlist.txt") as nounlist: + for noun in nounlist: + wordlist.append(noun) + +prefix_list = [] +for word in wordlist: + prefix_list.append(word[:min(len(word), 3)]) + +short_list = [] +short_list2 = [] +for prefix in prefix_list: + prefix = prefix.strip() + if len(short_list) < 700: + if (len(short_list) == 0 or short_list[-1] != prefix): + short_list.append(prefix) + else: + if ((len(short_list2) == 0 or short_list2[-1] != prefix) and short_list[-1] != prefix): + short_list2.append(prefix) + +with open("prefix_list_part1.txt", "w") as prefix_list_part1: + json.dump(short_list, prefix_list_part1) + +with open("prefix_list_part2.txt", "w") as prefix_list_part2: + json.dump(short_list2, prefix_list_part2) \ No newline at end of file diff --git a/Lambdas/Scraping/prefix_list_part1.txt b/Lambdas/Scraping/prefix_list_part1.txt new file mode 100644 index 0000000..ee83e0f --- /dev/null +++ b/Lambdas/Scraping/prefix_list_part1.txt @@ -0,0 +1 @@ +["ATM", "CD", "SUV", "TV", "aar", "aba", "abb", "abd", "abi", "abn", "abo", "abr", "abs", "abu", "aca", "acc", "ace", "ach", "aci", "ack", "aco", "acq", "acr", "act", "acu", "ad", "ada", "add", "adj", "adm", "ado", "adr", "adu", "adv", "aff", "afo", "aft", "age", "agg", "agl", "ago", "agr", "aid", "aim", "air", "ala", "alb", "alc", "ald", "ale", "alf", "alg", "ali", "all", "alm", "alp", "alt", "alu", "ama", "amb", "ame", "amm", "amn", "amo", "amu", "ana", "anc", "and", "ane", "ang", "ani", "ank", "ann", "ano", "ans", "ant", "anx", "any", "apa", "ape", "apo", "app", "apr", "aps", "aqu", "arc", "are", "arg", "ari", "ark", "arm", "arr", "art", "asc", "ash", "asi", "asp", "ass", "ast", "asy", "ate", "ath", "atm", "ato", "atr", "att", "auc", "aud", "aun", "aut", "ava", "ave", "avo", "awa", "awe", "axi", "azi", "bab", "bac", "bad", "baf", "bag", "bai", "bak", "bal", "bam", "ban", "bao", "bar", "bas", "bat", "bay", "bea", "bec", "bed", "bee", "beg", "beh", "bei", "bel", "ben", "ber", "bes", "bet", "bev", "bey", "bia", "bib", "bic", "bid", "bif", "bij", "bik", "bil", "bin", "bio", "bip", "bir", "bis", "bit", "bla", "ble", "bli", "blo", "blu", "boa", "bob", "bod", "bog", "bol", "bom", "bon", "boo", "bor", "bos", "bot", "bou", "bow", "box", "boy", "bra", "bre", "bri", "bro", "bru", "bub", "buc", "bud", "buf", "bug", "bui", "bul", "bum", "bun", "bur", "bus", "but", "buy", "buz", "c-c", "cab", "cac", "cad", "caf", "cag", "cak", "cal", "cam", "can", "cap", "car", "cas", "cat", "cau", "cav", "cay", "cei", "cel", "cem", "cen", "cep", "cer", "ces", "cha", "che", "chi", "cho", "chr", "chu", "cic", "cig", "cil", "cin", "cir", "cit", "civ", "cla", "cle", "cli", "clo", "clu", "co-", "coa", "cob", "coc", "cod", "coe", "cof", "coh", "coi", "cok", "col", "com", "con", "coo", "cop", "cor", "cos", "cot", "cou", "cov", "cow", "coy", "cra", "cre", "cri", "cro", "cru", "cry", "cub", "cuc", "cue", "cuf", "cui", "cul", "cum", "cup", "cur", "cus", "cut", "cyc", "cyg", "cyl", "cym", "cyn", "cys", "cyt", "dad", "daf", "dag", "dah", "dai", "dam", "dan", "dar", "das", "dat", "dau", "daw", "day", "dea", "deb", "dec", "ded", "dee", "def", "deg", "del", "dem", "den", "deo", "dep", "der", "des", "det", "dev", "dew", "dho", "dia", "dib", "dic", "die", "dif", "dig", "dil", "dim", "din", "dio", "dip", "dir", "dis", "div", "doc", "doe", "dog", "doi", "dol", "dom", "don", "doo", "dor", "dos", "dot", "dou", "dow", "doz", "dra", "dre", "dri", "dro", "dru", "dry", "duc", "dud", "due", "duf", "dug", "dul", "dum", "dun", "dup", "dur", "dus", "dut", "dwa", "dwe", "dyn", "dys", "e-b", "e-m", "e-r", "eag", "ear", "eas", "eat", "eav", "ecc", "ech", "ecl", "eco", "ect", "ecu", "edd", "edg", "edi", "edu", "eel", "eff", "egg", "ego", "eic", "eje", "elb", "eld", "ele", "elf", "eli", "elk", "ell", "elm", "elo", "elv", "ema", "emb", "eme", "emi", "emo", "emp", "emu", "ena", "enc", "end", "ene", "enf", "eng", "eni", "enj", "enq", "enr", "ent", "env", "enz", "epa", "epe", "eph", "epi", "epo", "equ", "era", "ere", "ero", "err", "esc", "esp", "ess", "est", "ete", "eth", "eup", "eur", "eva", "eve", "evi", "evo", "ex-", "exa", "exc", "exe", "exh", "exi", "exo", "exp", "ext", "eye", "eyr", "fab", "fac", "fah", "fai", "fal", "fam", "fan", "far", "fas", "fat", "fau", "fav", "faw", "fax", "fea", "fed", "fee", "fel", "fem", "fen", "fer", "fes", "fet", "few", "fib", "fic", "fid", "fie", "fif", "fig", "fil", "fin", "fir", "fis", "fit", "fix", "fla", "fle", "fli", "flo", "flu", "fly", "foa", "fob", "foc", "fog", "fol", "fon", "foo", "for", "fou", "fow", "fox", "fra", "fre", "fri", "fro", "fru", "fry", "fuc", "fue", "fug", "ful", "fun", "fur", "fus", "fut", "gad", "gaf", "gai", "gal", "gam", "gan", "gap", "gar", "gas", "gat", "gau", "gav", "gaz", "gea", "gee", "gel", "gem", "gen", "geo", "ger", "ges", "gey", "ghe", "gho", "gia", "gif", "gig", "gin", "gir", "git", "gla", "gle", "gli", "glo", "glu", "gna", "gnu", "go-", "goa", "gob", "god", "gog", "goi", "gol", "gon", "goo", "gop", "gor", "gos", "gov", "gow", "gra", "gre", "gri", "gro", "gru", "gua", "gue", "gui", "gum", "gun", "gut", "guy", "gym", "gyn", "gyr", "hab", "hac", "hai", "hak", "hal", "ham", "han", "hap", "har", "has", "hat", "hau", "hav", "haw", "hay", "haz", "hea", "hec", "hed", "hee", "hei", "hel", "hem", "hen", "hep", "her", "hes", "het", "hex", "hey", "hic", "hid", "hie", "hig", "hik", "hil", "hin", "hip", "hir", "his", "hit", "hiv", "hob", "hoc", "hoe", "hog", "hol", "hom", "hon", "hoo", "hop", "hor", "hos", "hot", "hou", "hov", "how", "hub", "hug", "hul", "hum", "hun", "hur", "hus", "hut", "hya", "hyb", "hyd", "hye", "hyg", "hyp", "ice", "ici", "ico", "icy", "id", "ide", "idi", "igl", "ign", "ike", "ill", "ima", "imb", "imi", "imm", "imp", "in-", "ina", "inb", "inc", "ind", "ine", "inf", "ing", "inh", "ini", "inj", "ink", "inl", "inn", "inp", "inq", "ins", "int", "inv", "iri", "iro", "irr", "isc", "isl", "iso"] \ No newline at end of file diff --git a/Lambdas/Scraping/prefix_list_part2.txt b/Lambdas/Scraping/prefix_list_part2.txt new file mode 100644 index 0000000..7bf12ad --- /dev/null +++ b/Lambdas/Scraping/prefix_list_part2.txt @@ -0,0 +1 @@ +["iss", "ite", "iti", "ivo", "jac", "jad", "jag", "jai", "jal", "jam", "jar", "jas", "jaw", "jaz", "jea", "jee", "jel", "jer", "jet", "jew", "jic", "jif", "job", "joc", "jod", "joe", "jog", "joi", "jok", "jot", "jou", "joy", "jud", "jug", "jui", "jul", "jum", "jun", "jur", "jus", "jut", "kal", "kam", "kan", "kar", "kay", "kaz", "keb", "kee", "ken", "ket", "key", "kic", "kid", "kie", "kil", "kim", "kin", "kio", "kis", "kit", "kiw", "kne", "kni", "kno", "knu", "koa", "koh", "kum", "lab", "lac", "lad", "lag", "lak", "lam", "lan", "lap", "lar", "las", "lat", "lau", "lav", "law", "lay", "lea", "lec", "lee", "lef", "leg", "lei", "lem", "len", "leo", "lep", "les", "let", "lev", "lia", "lib", "lic", "lid", "lie", "lif", "lig", "lik", "lil", "lim", "lin", "lio", "lip", "liq", "lis", "lit", "liv", "liz", "lla", "loa", "lob", "loc", "lod", "lof", "log", "loi", "lol", "lon", "loo", "loq", "lor", "los", "lot", "lou", "lov", "lox", "loy", "luc", "lug", "lum", "lun", "lus", "lut", "lux", "lyc", "lye", "lym", "lyn", "lyo", "lyr", "lys", "mRN", "mac", "mad", "mae", "mag", "mai", "maj", "mak", "mal", "mam", "man", "map", "mar", "mas", "mat", "max", "may", "mea", "mec", "med", "mee", "mel", "mem", "men", "mer", "mes", "met", "mez", "mic", "mid", "mig", "mil", "mim", "min", "mir", "mis", "mit", "mix", "moa", "mob", "moc", "mod", "mol", "mom", "mon", "moo", "mop", "mor", "mos", "mot", "mou", "mov", "mow", "moz", "mud", "muf", "mug", "muk", "mul", "mur", "mus", "mut", "myc", "mys", "myt", "nai", "nam", "nan", "nap", "nar", "nas", "nat", "nav", "nec", "nee", "neg", "nei", "neo", "nep", "ner", "nes", "net", "neu", "new", "nex", "nib", "nic", "nie", "nig", "nin", "nit", "nob", "nod", "noi", "non", "noo", "nor", "nos", "not", "nou", "nov", "nuc", "nud", "nuk", "num", "nun", "nur", "nut", "nyl", "nym", "oak", "oar", "oas", "oat", "obe", "obi", "obj", "obl", "obo", "obs", "occ", "oce", "oct", "odo", "ody", "oeu", "off", "oil", "okr", "old", "ole", "oli", "ome", "omi", "omn", "onc", "oni", "onl", "ons", "ope", "oph", "opi", "opo", "opp", "opt", "ora", "orc", "ord", "ore", "org", "ori", "orn", "osm", "osp", "ost", "oth", "ott", "oun", "out", "ova", "ove", "owl", "own", "ox", "oxf", "oxy", "oys", "ozo", "pac", "pad", "pag", "pai", "paj", "pal", "pam", "pan", "pap", "par", "pas", "pat", "pau", "pav", "paw", "pay", "pea", "pec", "ped", "pee", "peg", "pel", "pen", "peo", "pep", "per", "pes", "pet", "pew", "pha", "phe", "phi", "pho", "phr", "phy", "pia", "pic", "pie", "pig", "pik", "pil", "pim", "pin", "pio", "pip", "pir", "pis", "pit", "piz", "pla", "ple", "pli", "plo", "plu", "ply", "pne", "poc", "pod", "poe", "poi", "pok", "pol", "pom", "pon", "poo", "pop", "por", "pos", "pot", "pou", "pov", "pow", "pra", "pre", "pri", "pro", "pru", "pse", "psy", "pta", "pub", "pud", "puf", "pug", "pul", "pum", "pun", "pup", "pur", "pus", "put", "puz", "pyr", "qua", "que", "qui", "quo", "rab", "rac", "rad", "raf", "rag", "rai", "rak", "ral", "ram", "ran", "rap", "ras", "rat", "rav", "raw", "ray", "raz", "rea", "reb", "rec", "red", "ree", "ref", "reg", "reh", "rei", "rej", "rel", "rem", "ren", "reo", "rep", "req", "res", "ret", "reu", "rev", "rew", "rhe", "rhi", "rhu", "rhy", "rib", "ric", "rid", "rif", "rig", "rim", "rin", "rio", "rip", "ris", "rit", "riv", "roa", "rob", "roc", "rod", "rol", "rom", "roo", "rop", "ros", "rot", "rou", "row", "rub", "ruc", "rud", "ruf", "rug", "rui", "rul", "rum", "run", "rus", "rut", "rye", "sab", "sac", "sad", "saf", "sag", "sai", "sak", "sal", "sam", "san", "sar", "sas", "sat", "sau", "sav", "saw", "sax", "sca", "sce", "sch", "sci", "sco", "scr", "scu", "sea", "sec", "sed", "see", "seg", "sei", "sel", "sem", "sen", "sep", "seq", "ser", "ses", "set", "sev", "sew", "sex", "sha", "she", "shi", "sho", "shr", "shu", "sib", "sic", "sid", "sie", "sig", "sil", "sim", "sin", "sip", "sir", "sis", "sit", "siz", "ska", "ske", "ski", "sku", "sky", "sla", "sle", "sli", "slo", "slu", "sme", "smi", "smo", "smu", "sna", "sne", "sni", "sno", "snu", "soa", "soc", "sod", "sof", "soi", "sol", "som", "son", "soo", "sop", "sor", "sou", "sov", "sow", "soy", "spa", "spe", "sph", "spi", "spl", "spo", "spr", "spu", "spy", "squ", "sta", "ste", "sti", "sto", "str", "stu", "sty", "sub", "suc", "sue", "suf", "sug", "sui", "sul", "sum", "sun", "sup", "sur", "sus", "swa", "swe", "swi", "swo", "syc", "sym", "syn", "syr", "sys", "t-s", "tab", "tac", "tad", "tag", "tai", "tak", "tal", "tam", "tan", "tap", "tar", "tas", "tat", "tav", "tax", "tea", "tec", "tee", "tel", "tem", "ten", "tep", "ter", "tes", "tex", "tha", "the", "thi", "tho", "thr", "thu", "thy", "tia", "tic", "tid", "tie", "tig", "til", "tim", "tin", "tip", "tir", "tis", "tit", "toa", "tob", "tod", "toe", "tof", "tog", "toi", "tol", "tom", "ton", "too", "top", "toq", "tor", "tos", "tot", "tou", "tow", "toy", "tra", "tre", "tri", "tro", "tru", "try", "tsu", "tub", "tug", "tui", "tul", "tum", "tun", "tur", "tus", "tut", "tux", "twe", "twi", "typ", "uku", "ult", "umb", "unb", "unc", "und", "une", "uni", "upd", "upg", "upl", "upp", "ups", "upw", "urg", "urn", "usa", "use", "ush", "usu", "ute", "uti", "vac", "vag", "val", "vam", "van", "var", "vas", "vau", "vea", "vec", "veg", "veh", "vei", "vel", "ven", "ver", "ves", "vet", "via", "vib", "vic", "vid", "vie", "vig", "vil", "vin", "vio", "vir", "vis", "vit", "viv", "vix", "vod", "vog", "voi", "vol", "vom", "vot", "voy", "vul", "wad", "waf", "wag", "wai", "wak", "wal", "wam", "wan", "war", "was", "wat", "wav", "wax", "way", "wea", "web", "wed", "wee", "wei", "wel", "wes", "wet", "wha", "whe", "whi", "who", "wic", "wid", "wif", "wil", "win", "wir", "wis", "wit", "wok", "wol", "wom", "won", "woo", "wor", "wou", "wra", "wre", "wri", "wro", "xyl", "yac", "yah", "yak", "yam", "yan", "yar", "yaw", "yea", "yel", "yes", "yew", "yin", "yog", "yok", "yol", "you", "yoy", "yur", "zam", "zeb", "zen", "zep", "zer", "zig", "zin", "zip", "zit", "zom", "zon", "zoo", "zuc"] \ No newline at end of file diff --git a/Lambdas/Scraping/runOrchestrator.py b/Lambdas/Scraping/runOrchestrator.py new file mode 100644 index 0000000..b4d8b7f --- /dev/null +++ b/Lambdas/Scraping/runOrchestrator.py @@ -0,0 +1,19 @@ +import json +import boto3 + +def lambda_handler(event, context): + with open("words.txt") as words_file: + words = json.load(words_file) + print(words) + for word in words: + client = boto3.client('lambda') + response = client.invoke( + FunctionName='KohlsScraper', + InvocationType="Event", + LogType="None", + Payload= """{"toScrape": \"""" + word + "\"}" + ) + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda!') + } \ No newline at end of file diff --git a/Lambdas/Scraping/setupBuildFolder.sh b/Lambdas/Scraping/setupBuildFolder.sh new file mode 100644 index 0000000..fc7f142 --- /dev/null +++ b/Lambdas/Scraping/setupBuildFolder.sh @@ -0,0 +1,7 @@ +#!/bin/bash +#Currently to be run only from the Scraping directory +mkdir build +pip3 install --target build requests +pip3 install --target build PyMySQL +pip3 install --target build beautifulsoup4 +mkdir artifacts From 4e7b180fe086af6f34a8735b4f9aa0ff7cfd73d3 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 20 Oct 2020 05:45:33 -0400 Subject: [PATCH 04/46] Async load search results --- .../com/example/listify/SearchResults.java | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/SearchResults.java b/Listify/app/src/main/java/com/example/listify/SearchResults.java index 0b312c6..3841f4a 100644 --- a/Listify/app/src/main/java/com/example/listify/SearchResults.java +++ b/Listify/app/src/main/java/com/example/listify/SearchResults.java @@ -22,7 +22,7 @@ import java.util.Properties; import static com.example.listify.MainActivity.am; -public class SearchResults extends AppCompatActivity implements SortDialogFragment.OnSortingListener { +public class SearchResults extends AppCompatActivity implements SortDialogFragment.OnSortingListener, Requestor.Receiver { private ListView listView; private SearchResultsListAdapter searchResultsListAdapter; private List resultsProductList = new ArrayList<>(); @@ -148,32 +148,7 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme } Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); - - SynchronousReceiver itemReceiver = new SynchronousReceiver<>(); - requestor.getObject(query, ItemSearch.class, itemReceiver, itemReceiver); - ItemSearch results; - try { - results = itemReceiver.await(); - for (int i = 0; i < results.getResults().size(); i++) { - // TODO: Change to dynamically grab chain name by id - resultsProductList.add(new Product(results.getResults().get(i).getDescription(), results.getResults().get(i).getProductID(), "Kroger", results.getResults().get(i).getChainID(), results.getResults().get(i).getUpc(), results.getResults().get(i).getDescription(), results.getResults().get(i).getPrice(), results.getResults().get(i).getImageURL(), results.getResults().get(i).getDepartment())); - } - } catch (Exception e) { - e.printStackTrace(); - } - - // Create a list of all stores in the results so the user can filter by store name - for (int i = 0; i < resultsProductList.size(); i++) { - if (!stores.contains(resultsProductList.get(i).getChainName())) { - stores.add(resultsProductList.get(i).getChainName()); - } - } - - // Add all results to the sorted list - resultsProductListSorted.addAll(resultsProductList); - - // Apply selected sorting to the list - sortResults(); + requestor.getObject(query, ItemSearch.class, this); } // Sorts the search results @@ -258,6 +233,52 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme resultsProductListSorted.addAll(temp); } - searchResultsListAdapter.notifyDataSetChanged(); + // Updates the list of search results. Runs on the main UI thread since other threads are + // not allowed to change UI elements + runOnUiThread(new Runnable() { + @Override + public void run() { + searchResultsListAdapter.notifyDataSetChanged(); + } + }); + } + + // This is called after the search results come back from the server + // TODO: Display a "no results" message if nothing is found when searching + @Override + public void acceptDelivery(Object delivered) { + ItemSearch results = (ItemSearch) delivered; + + try { + for (int i = 0; i < results.getResults().size(); i++) { + // TODO: Change to dynamically grab chain name by id + resultsProductList.add(new Product( + results.getResults().get(i).getDescription(), + results.getResults().get(i).getProductID(), + "Kroger", + results.getResults().get(i).getChainID(), + results.getResults().get(i).getUpc(), + results.getResults().get(i).getDescription(), + results.getResults().get(i).getPrice(), + results.getResults().get(i).getImageURL(), + results.getResults().get(i).getDepartment() + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + + // Create a list of all stores in the results so the user can filter by store name + for (int i = 0; i < resultsProductList.size(); i++) { + if (!stores.contains(resultsProductList.get(i).getChainName())) { + stores.add(resultsProductList.get(i).getChainName()); + } + } + + // Add all results to the sorted list + resultsProductListSorted.addAll(resultsProductList); + + // Apply selected sorting to the list + sortResults(); } } \ No newline at end of file From 4757ba2ddf15041af7d48c32f8d07067444e4924 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 20 Oct 2020 07:28:58 -0400 Subject: [PATCH 05/46] Create new thread on search and show progress bar --- .../com/example/listify/SearchResults.java | 38 ++++++++++++++----- .../res/layout/content_search_results.xml | 10 +++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/SearchResults.java b/Listify/app/src/main/java/com/example/listify/SearchResults.java index 3841f4a..37f78cb 100644 --- a/Listify/app/src/main/java/com/example/listify/SearchResults.java +++ b/Listify/app/src/main/java/com/example/listify/SearchResults.java @@ -9,6 +9,7 @@ import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; +import android.widget.ProgressBar; import android.widget.SearchView; import com.example.listify.adapter.SearchResultsListAdapter; import com.example.listify.data.ItemSearch; @@ -23,7 +24,7 @@ import java.util.Properties; import static com.example.listify.MainActivity.am; public class SearchResults extends AppCompatActivity implements SortDialogFragment.OnSortingListener, Requestor.Receiver { - private ListView listView; + private ProgressBar loadingSearch; private SearchResultsListAdapter searchResultsListAdapter; private List resultsProductList = new ArrayList<>(); private List resultsProductListSorted = new ArrayList<>(); @@ -31,6 +32,7 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme private int storeSelection; private int sortMode; private boolean descending; + boolean doneLoading = false; @Override public void sendSort(int storeSelection, int sortMode, boolean descending) { @@ -47,6 +49,8 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); + loadingSearch = (ProgressBar) findViewById(R.id.progress_loading_search); + // Back button closes this activity and returns to previous activity (MainActivity) ImageButton backButton = (ImageButton) findViewById(R.id.backToHomeButton); backButton.setOnClickListener(new View.OnClickListener() { @@ -77,7 +81,7 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme } }); - listView = (ListView) findViewById(R.id.search_results_list); + ListView listView = (ListView) findViewById(R.id.search_results_list); searchResultsListAdapter = new SearchResultsListAdapter(this, resultsProductListSorted); listView.setAdapter(searchResultsListAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @@ -96,7 +100,15 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - doSearch(query); + // Show progress bar + loadingSearch.setVisibility(View.VISIBLE); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + doSearch(query); + } + }); + t.start(); return false; } @@ -122,12 +134,8 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme sortDialog.show(getSupportFragmentManager(), "Sort"); } }); - } - - - // Override default phone back button to add animation @Override public void onBackPressed() { @@ -136,10 +144,13 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme } private void doSearch(String query) { - // Clear the old search results resultsProductList.clear(); + // Clear old search results from the view + resultsProductListSorted.clear(); + searchResultsListAdapter.notifyDataSetChanged(); + Properties configs = new Properties(); try { configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json"); @@ -178,7 +189,6 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme }); break; - // TODO: May need to change this depending on if price is stored as a string or a double case 2: resultsProductListSorted.sort(new Comparator() { @Override @@ -238,7 +248,13 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme runOnUiThread(new Runnable() { @Override public void run() { - searchResultsListAdapter.notifyDataSetChanged(); + if (doneLoading) { + doneLoading = false; + searchResultsListAdapter.notifyDataSetChanged(); + + // Hide progress bar + loadingSearch.setVisibility(View.GONE); + } } }); } @@ -280,5 +296,7 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme // Apply selected sorting to the list sortResults(); + + doneLoading = true; } } \ No newline at end of file diff --git a/Listify/app/src/main/res/layout/content_search_results.xml b/Listify/app/src/main/res/layout/content_search_results.xml index cd08592..51ff208 100644 --- a/Listify/app/src/main/res/layout/content_search_results.xml +++ b/Listify/app/src/main/res/layout/content_search_results.xml @@ -12,6 +12,16 @@ tools:context=".SearchResults" tools:showIn="@layout/activity_search_results"> + + Date: Thu, 22 Oct 2020 16:28:00 -0400 Subject: [PATCH 06/46] async search bug fixes --- .../com/example/listify/SearchResults.java | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/SearchResults.java b/Listify/app/src/main/java/com/example/listify/SearchResults.java index 37f78cb..6b62c74 100644 --- a/Listify/app/src/main/java/com/example/listify/SearchResults.java +++ b/Listify/app/src/main/java/com/example/listify/SearchResults.java @@ -32,7 +32,6 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme private int storeSelection; private int sortMode; private boolean descending; - boolean doneLoading = false; @Override public void sendSort(int storeSelection, int sortMode, boolean descending) { @@ -102,6 +101,14 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme public boolean onQueryTextSubmit(String query) { // Show progress bar loadingSearch.setVisibility(View.VISIBLE); + + // Clear the old search results + resultsProductList.clear(); + + // Clear old search results from the view + resultsProductListSorted.clear(); + searchResultsListAdapter.notifyDataSetChanged(); + Thread t = new Thread(new Runnable() { @Override public void run() { @@ -144,13 +151,6 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme } private void doSearch(String query) { - // Clear the old search results - resultsProductList.clear(); - - // Clear old search results from the view - resultsProductListSorted.clear(); - searchResultsListAdapter.notifyDataSetChanged(); - Properties configs = new Properties(); try { configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json"); @@ -242,21 +242,6 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme resultsProductListSorted.clear(); resultsProductListSorted.addAll(temp); } - - // Updates the list of search results. Runs on the main UI thread since other threads are - // not allowed to change UI elements - runOnUiThread(new Runnable() { - @Override - public void run() { - if (doneLoading) { - doneLoading = false; - searchResultsListAdapter.notifyDataSetChanged(); - - // Hide progress bar - loadingSearch.setVisibility(View.GONE); - } - } - }); } // This is called after the search results come back from the server @@ -297,6 +282,17 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme // Apply selected sorting to the list sortResults(); - doneLoading = true; + // Updates the list of search results. Runs on the main UI thread since other threads are + // not allowed to change UI elements + runOnUiThread(new Runnable() { + @Override + public void run() { + searchResultsListAdapter.notifyDataSetChanged(); + + // Hide progress bar + loadingSearch.setVisibility(View.GONE); + + } + }); } } \ No newline at end of file From ec28e8babca7f2aca1f4009a7812bb72e0af3032 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Thu, 22 Oct 2020 18:06:10 -0400 Subject: [PATCH 07/46] Async loading lists page --- .../listify/ui/lists/ListsFragment.java | 77 ++++++++++++------- .../src/main/res/layout/fragment_lists.xml | 10 +++ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java index c7029ba..e542c0a 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java +++ b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java @@ -7,9 +7,11 @@ import android.view.ViewGroup; import android.view.LayoutInflater; import android.widget.AdapterView; import android.widget.ListView; +import android.widget.ProgressBar; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.fragment.app.ListFragment; import com.amplifyframework.auth.AuthException; import com.example.listify.AuthManager; @@ -34,16 +36,19 @@ import java.util.Properties; import static com.example.listify.MainActivity.am; -public class ListsFragment extends Fragment implements CreateListDialogFragment.OnNewListListener { +public class ListsFragment extends Fragment implements CreateListDialogFragment.OnNewListListener, Requestor.Receiver { ArrayList shoppingLists = new ArrayList<>(); DisplayShoppingListsAdapter displayShoppingListsAdapter; + Requestor requestor; ListView shoppingListsView; + ProgressBar loadingLists; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_lists, container, false); shoppingListsView = root.findViewById(R.id.shopping_lists); + loadingLists = (ProgressBar) root.findViewById(R.id.progress_loading_lists); + loadingLists.setVisibility(View.VISIBLE); - // TODO: Switch this to async Properties configs = new Properties(); try { configs = AuthManager.loadProperties(getContext(), "android.resource://" + getActivity().getPackageName() + "/raw/auths.json"); @@ -51,35 +56,18 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. e.printStackTrace(); } - Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); + requestor = new Requestor(am, configs.getProperty("apiKey")); SynchronousReceiver listIdsReceiver = new SynchronousReceiver<>(); - SynchronousReceiver listReceiver = new SynchronousReceiver<>(); - requestor.getListOfIds(List.class, listIdsReceiver, listIdsReceiver); - try { - Integer[] listIds = listIdsReceiver.await(); - for (int i = 0; i < listIds.length; i++) { - requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); - shoppingLists.add(listReceiver.await()); - } - } catch (Exception e) { - e.printStackTrace(); - } - - - // Set adapter and display this users lists - displayShoppingListsAdapter = new DisplayShoppingListsAdapter(getActivity(), shoppingLists); - shoppingListsView.setAdapter(displayShoppingListsAdapter); - shoppingListsView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + final Requestor.Receiver recv = this; + Thread t = new Thread(new Runnable() { @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Intent listPage = new Intent(getContext(), ListPage.class); - - // Send the list ID - listPage.putExtra("listID", shoppingLists.get(position).getItemID()); - startActivity(listPage); + public void run() { + requestor.getListOfIds(List.class, recv, null); } }); + t.start(); + FloatingActionButton fab = (FloatingActionButton) root.findViewById(R.id.new_list_fab); Fragment thisFragment = this; @@ -120,4 +108,41 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. e.printStackTrace(); } } + + @Override + public void acceptDelivery(Object delivered) { + SynchronousReceiver listReceiver = new SynchronousReceiver<>(); + Integer[] listIds = (Integer[]) delivered; + try { +// Integer[] listIds = listIdsReceiver.await(); + for (int i = 0; i < listIds.length; i++) { + requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); + shoppingLists.add(listReceiver.await()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + // Set adapter and display this users lists + displayShoppingListsAdapter = new DisplayShoppingListsAdapter(getActivity(), shoppingLists); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + shoppingListsView.setAdapter(displayShoppingListsAdapter); + shoppingListsView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Intent listPage = new Intent(getContext(), ListPage.class); + + // Send the list ID + listPage.putExtra("listID", shoppingLists.get(position).getItemID()); + startActivity(listPage); + } + }); + loadingLists.setVisibility(View.GONE); + } + }); + + } } \ No newline at end of file diff --git a/Listify/app/src/main/res/layout/fragment_lists.xml b/Listify/app/src/main/res/layout/fragment_lists.xml index 0793341..e595fb9 100644 --- a/Listify/app/src/main/res/layout/fragment_lists.xml +++ b/Listify/app/src/main/res/layout/fragment_lists.xml @@ -12,6 +12,16 @@ tools:context=".ui.lists.ListsFragment" tools:showIn="@layout/fragment_lists"> + + Date: Thu, 22 Oct 2020 18:36:00 -0400 Subject: [PATCH 08/46] Async loading list products --- .../java/com/example/listify/ListPage.java | 78 ++++++++++--------- .../app/src/main/res/layout/activity_list.xml | 14 +++- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index f81c481..e05d1f6 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -21,13 +21,14 @@ import java.util.Properties; import static com.example.listify.MainActivity.am; -public class ListPage extends AppCompatActivity { +public class ListPage extends AppCompatActivity implements Requestor.Receiver { ListView listView; MyAdapter myAdapter; Button incrQuan; Button decrQuan; Button removeItem; + ProgressBar loadingListItems; ArrayList pNames = new ArrayList<>(); ArrayList pStores = new ArrayList<>(); @@ -39,11 +40,23 @@ public class ListPage extends AppCompatActivity { Requestor requestor; + // TODO: Display a message if their list is empty @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // Read list ID from caller final int listID = (int) getIntent().getSerializableExtra("listID"); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_list); + + listView = findViewById(R.id.listView); + myAdapter = new MyAdapter(this, pNames, pStores, pPrices, pQuantity, pImages); + + listView.setAdapter(myAdapter); + + loadingListItems = findViewById(R.id.progress_loading_list_items); + loadingListItems.setVisibility(View.VISIBLE); + Properties configs = new Properties(); try { configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json"); @@ -51,18 +64,31 @@ public class ListPage extends AppCompatActivity { e.printStackTrace(); } requestor = new Requestor(am, configs.getProperty("apiKey")); - SynchronousReceiver lr = new SynchronousReceiver<>(); //ListReceiver lr = new ListReceiver<>(); - requestor.getObject(Integer.toString(listID), List.class, lr); + requestor.getObject(Integer.toString(listID), List.class, this); - List list; + /*pNames.add("Half-gallon organic whole milk"); + pStores.add("Kroger"); + pPrices.add("$5.00"); + pQuantity.add("1"); + pImages.add(R.drawable.milk); - try { - list = lr.await(); - } - catch (Exception e) { - list = null; - } + pNames.add("5-bunch medium bananas"); + pStores.add("Kroger"); + pPrices.add("$3.00"); + pQuantity.add("1"); + pImages.add(R.drawable.bananas); + + pNames.add("JIF 40-oz creamy peanut butter"); + pStores.add("Kroger"); + pPrices.add("$7.00"); + pQuantity.add("1"); + pImages.add(R.drawable.peanutbutter);*/ + } + + @Override + public void acceptDelivery(Object delivered) { + List list = (List) delivered; if(list != null) { for (ListEntry entry : list.getEntries()) { @@ -87,31 +113,13 @@ public class ListPage extends AppCompatActivity { } } - /*pNames.add("Half-gallon organic whole milk"); - pStores.add("Kroger"); - pPrices.add("$5.00"); - pQuantity.add("1"); - pImages.add(R.drawable.milk); - - pNames.add("5-bunch medium bananas"); - pStores.add("Kroger"); - pPrices.add("$3.00"); - pQuantity.add("1"); - pImages.add(R.drawable.bananas); - - pNames.add("JIF 40-oz creamy peanut butter"); - pStores.add("Kroger"); - pPrices.add("$7.00"); - pQuantity.add("1"); - pImages.add(R.drawable.peanutbutter);*/ - - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_list); - - listView = findViewById(R.id.listView); - myAdapter = new MyAdapter(this, pNames, pStores, pPrices, pQuantity, pImages); - - listView.setAdapter(myAdapter); + runOnUiThread(new Runnable() { + @Override + public void run() { + loadingListItems.setVisibility(View.GONE); + myAdapter.notifyDataSetChanged(); + } + }); } class MyAdapter extends ArrayAdapter { diff --git a/Listify/app/src/main/res/layout/activity_list.xml b/Listify/app/src/main/res/layout/activity_list.xml index 65c12f4..97ce5b1 100644 --- a/Listify/app/src/main/res/layout/activity_list.xml +++ b/Listify/app/src/main/res/layout/activity_list.xml @@ -1,9 +1,19 @@ - + + - \ No newline at end of file + \ No newline at end of file From 8604d98e6348da8eb45fe92976291642c76dafeb Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Thu, 22 Oct 2020 23:09:57 -0400 Subject: [PATCH 09/46] Async creating lists and adding items --- .../java/com/example/listify/ItemDetails.java | 96 ++++++++++++++----- .../listify/ui/lists/ListsFragment.java | 49 +++++++++- .../src/main/res/layout/dialog_loading.xml | 16 ++++ .../main/res/menu/activity_main_drawer.xml | 10 +- 4 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 Listify/app/src/main/res/layout/dialog_loading.xml diff --git a/Listify/app/src/main/java/com/example/listify/ItemDetails.java b/Listify/app/src/main/java/com/example/listify/ItemDetails.java index 415d1a4..15f0767 100644 --- a/Listify/app/src/main/java/com/example/listify/ItemDetails.java +++ b/Listify/app/src/main/java/com/example/listify/ItemDetails.java @@ -1,5 +1,8 @@ package com.example.listify; +import android.app.Dialog; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import com.amplifyframework.auth.AuthException; @@ -11,6 +14,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -71,6 +76,17 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr @Override public void onClick(View v) { closeFABMenu(); + // Create and show a loading dialog + Dialog loadingDialog = new Dialog(ItemDetails.this); + loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); + loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + // layout to display + loadingDialog.setContentView(R.layout.dialog_loading); + // set color transpartent + loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + loadingDialog.setCancelable(false); + loadingDialog.setCanceledOnTouchOutside(false); + loadingDialog.show(); Properties configs = new Properties(); try { @@ -82,20 +98,27 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); SynchronousReceiver listIdsReceiver = new SynchronousReceiver<>(); SynchronousReceiver listReceiver = new SynchronousReceiver<>(); - requestor.getListOfIds(List.class, listIdsReceiver, listIdsReceiver); - try { - Integer[] listIds = listIdsReceiver.await(); - for (int i = 0; i < listIds.length; i++) { - requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); - shoppingLists.add(listReceiver.await()); - } - } catch (Exception e) { - e.printStackTrace(); - } - ListPickerDialogFragment listPickerDialog = new ListPickerDialogFragment(shoppingLists); - listPickerDialog.show(getSupportFragmentManager(), "User Lists"); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + Integer[] listIds = listIdsReceiver.await(); + for (int i = 0; i < listIds.length; i++) { + requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); + shoppingLists.add(listReceiver.await()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + loadingDialog.cancel(); + ListPickerDialogFragment listPickerDialog = new ListPickerDialogFragment(shoppingLists); + listPickerDialog.show(getSupportFragmentManager(), "User Lists"); + } + }); + t.start(); } }); @@ -184,6 +207,17 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr // Create a new list and add the item to it @Override public void sendNewListName(String name, int quantity) { + // Create and show a loading dialog + Dialog loadingDialog = new Dialog(this); + loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); + loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + // layout to display + loadingDialog.setContentView(R.layout.dialog_loading); + // set color transpartent + loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + loadingDialog.setCancelable(false); + loadingDialog.setCanceledOnTouchOutside(false); + loadingDialog.show(); Properties configs = new Properties(); try { @@ -196,16 +230,34 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr com.example.listify.data.List newList = new List(-1, name, "user filled by lambda", Instant.now().toEpochMilli()); - try { - requestor.postObject(newList, idReceiver, idReceiver); - int newListId = idReceiver.await(); - ListEntry entry = new ListEntry(newListId, curProduct.getItemId(), quantity, Instant.now().toEpochMilli(),false); - requestor.postObject(entry); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + requestor.postObject(newList, idReceiver, idReceiver); + int newListId = idReceiver.await(); + ListEntry entry = new ListEntry(newListId, curProduct.getItemId(), quantity, Instant.now().toEpochMilli(),false); + requestor.postObject(entry); - Toast.makeText(this, String.format("%s created and item added", name), Toast.LENGTH_LONG).show(); - } catch (Exception e) { - Toast.makeText(this, "An error occurred", Toast.LENGTH_LONG).show(); - e.printStackTrace(); - } + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ItemDetails.this, String.format("%s created and item added", name), Toast.LENGTH_LONG).show(); + loadingDialog.cancel(); + } + }); + } catch (Exception e) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ItemDetails.this, "An error occurred", Toast.LENGTH_LONG).show(); + loadingDialog.cancel(); + e.printStackTrace(); + } + }); + } + } + }); + t.start(); } } \ No newline at end of file diff --git a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java index e542c0a..6c1c2c1 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java +++ b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java @@ -1,10 +1,15 @@ package com.example.listify.ui.lists; +import android.app.Dialog; import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater; +import android.view.Window; +import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ListView; import android.widget.ProgressBar; @@ -85,6 +90,17 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. @Override public void sendNewListName(String name) { + // Create and show a loading dialog + Dialog loadingDialog = new Dialog(getActivity()); + loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); + loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + // layout to display + loadingDialog.setContentView(R.layout.dialog_loading); + // set color transpartent + loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + loadingDialog.setCancelable(false); + loadingDialog.setCanceledOnTouchOutside(false); + loadingDialog.show(); Properties configs = new Properties(); try { @@ -99,14 +115,39 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. try { requestor.postObject(newList, idReceiver, idReceiver); - newList.setItemID(idReceiver.await()); - shoppingLists.add(newList); - displayShoppingListsAdapter.notifyDataSetChanged(); - Toast.makeText(getContext(), String.format("%s created", name), Toast.LENGTH_LONG).show(); } catch (Exception e) { Toast.makeText(getContext(), "An error occurred", Toast.LENGTH_LONG).show(); e.printStackTrace(); } + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + newList.setItemID(idReceiver.await()); + } catch (Exception e) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getContext(), "An error occurred", Toast.LENGTH_LONG).show(); + loadingDialog.cancel(); + } + }); + e.printStackTrace(); + + } + shoppingLists.add(newList); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + displayShoppingListsAdapter.notifyDataSetChanged(); + loadingDialog.cancel(); + Toast.makeText(getContext(), String.format("%s created", name), Toast.LENGTH_LONG).show(); + } + }); + } + }); + t.start(); } @Override diff --git a/Listify/app/src/main/res/layout/dialog_loading.xml b/Listify/app/src/main/res/layout/dialog_loading.xml new file mode 100644 index 0000000..a51c2a1 --- /dev/null +++ b/Listify/app/src/main/res/layout/dialog_loading.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/Listify/app/src/main/res/menu/activity_main_drawer.xml b/Listify/app/src/main/res/menu/activity_main_drawer.xml index 93efe47..f2e00a0 100644 --- a/Listify/app/src/main/res/menu/activity_main_drawer.xml +++ b/Listify/app/src/main/res/menu/activity_main_drawer.xml @@ -20,10 +20,10 @@ android:id="@+id/nav_lists" android:icon="@drawable/ic_baseline_list_alt_28" android:title="@string/menu_lists" /> - + + + + + \ No newline at end of file From 6b30476ccd68581040ec58689fb26e05d8e1c8bf Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Fri, 23 Oct 2020 22:27:01 -0400 Subject: [PATCH 10/46] Move loading circle into its own object --- .../java/com/example/listify/ItemDetails.java | 24 ++----------- .../example/listify/LoadingCircleDialog.java | 36 +++++++++++++++++++ .../listify/ui/lists/ListsFragment.java | 12 ++----- 3 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 Listify/app/src/main/java/com/example/listify/LoadingCircleDialog.java diff --git a/Listify/app/src/main/java/com/example/listify/ItemDetails.java b/Listify/app/src/main/java/com/example/listify/ItemDetails.java index 15f0767..e76f699 100644 --- a/Listify/app/src/main/java/com/example/listify/ItemDetails.java +++ b/Listify/app/src/main/java/com/example/listify/ItemDetails.java @@ -76,16 +76,7 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr @Override public void onClick(View v) { closeFABMenu(); - // Create and show a loading dialog - Dialog loadingDialog = new Dialog(ItemDetails.this); - loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); - loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - // layout to display - loadingDialog.setContentView(R.layout.dialog_loading); - // set color transpartent - loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - loadingDialog.setCancelable(false); - loadingDialog.setCanceledOnTouchOutside(false); + LoadingCircleDialog loadingDialog = new LoadingCircleDialog(ItemDetails.this); loadingDialog.show(); Properties configs = new Properties(); @@ -180,7 +171,7 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr } - // Add the viewed item to the selected list + // Add the selected item to the selected list @Override public void sendListSelection(int selectedListIndex, int quantity) { @@ -207,16 +198,7 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr // Create a new list and add the item to it @Override public void sendNewListName(String name, int quantity) { - // Create and show a loading dialog - Dialog loadingDialog = new Dialog(this); - loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); - loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - // layout to display - loadingDialog.setContentView(R.layout.dialog_loading); - // set color transpartent - loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - loadingDialog.setCancelable(false); - loadingDialog.setCanceledOnTouchOutside(false); + LoadingCircleDialog loadingDialog = new LoadingCircleDialog(this); loadingDialog.show(); Properties configs = new Properties(); diff --git a/Listify/app/src/main/java/com/example/listify/LoadingCircleDialog.java b/Listify/app/src/main/java/com/example/listify/LoadingCircleDialog.java new file mode 100644 index 0000000..d4f6d82 --- /dev/null +++ b/Listify/app/src/main/java/com/example/listify/LoadingCircleDialog.java @@ -0,0 +1,36 @@ +package com.example.listify; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.view.Window; +import android.view.WindowManager; + +public class LoadingCircleDialog { + Dialog loadingDialog; + + public LoadingCircleDialog(Context context) { + loadingDialog = new Dialog(context); + + // Create and show a loading dialog + loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); + loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + + // layout to display + loadingDialog.setContentView(R.layout.dialog_loading); + + // set color transpartent + loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + loadingDialog.setCancelable(false); + loadingDialog.setCanceledOnTouchOutside(false); + } + + public void show() { + loadingDialog.show(); + } + + public void cancel() { + loadingDialog.cancel(); + } +} diff --git a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java index 6c1c2c1..0e1ccbf 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java +++ b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java @@ -24,6 +24,7 @@ import com.example.listify.CreateListAddDialogFragment; import com.example.listify.CreateListDialogFragment; import com.example.listify.ItemDetails; import com.example.listify.ListPage; +import com.example.listify.LoadingCircleDialog; import com.example.listify.R; import com.example.listify.Requestor; import com.example.listify.SearchResults; @@ -90,16 +91,7 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. @Override public void sendNewListName(String name) { - // Create and show a loading dialog - Dialog loadingDialog = new Dialog(getActivity()); - loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); - loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - // layout to display - loadingDialog.setContentView(R.layout.dialog_loading); - // set color transpartent - loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - loadingDialog.setCancelable(false); - loadingDialog.setCanceledOnTouchOutside(false); + LoadingCircleDialog loadingDialog = new LoadingCircleDialog(getActivity()); loadingDialog.show(); Properties configs = new Properties(); From 8f6ecdbab1dbba9cb2f5a3751d45d14be166e717 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Fri, 23 Oct 2020 20:20:31 -0700 Subject: [PATCH 11/46] Removed unnecessary code for Cancel button in pop-up window --- .../main/java/com/example/listify/ui/ForgotPasswordPage.java | 4 +--- .../app/src/main/java/com/example/listify/ui/SignupPage.java | 4 +--- .../main/java/com/example/listify/ui/home/HomeFragment.java | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ui/ForgotPasswordPage.java b/Listify/app/src/main/java/com/example/listify/ui/ForgotPasswordPage.java index 7b58531..fe59b9c 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/ForgotPasswordPage.java +++ b/Listify/app/src/main/java/com/example/listify/ui/ForgotPasswordPage.java @@ -84,9 +84,7 @@ public class ForgotPasswordPage extends AppCompatActivity { }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { - return; - } + public void onClick(DialogInterface dialog, int which) {} }); AlertDialog dialog = builder.create(); dialog.show(); diff --git a/Listify/app/src/main/java/com/example/listify/ui/SignupPage.java b/Listify/app/src/main/java/com/example/listify/ui/SignupPage.java index e438f7a..6e1eaeb 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/SignupPage.java +++ b/Listify/app/src/main/java/com/example/listify/ui/SignupPage.java @@ -103,9 +103,7 @@ public class SignupPage extends AppCompatActivity { }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { - return; - } + public void onClick(DialogInterface dialog, int which) {} }); AlertDialog dialog = builder.create(); dialog.show(); diff --git a/Listify/app/src/main/java/com/example/listify/ui/home/HomeFragment.java b/Listify/app/src/main/java/com/example/listify/ui/home/HomeFragment.java index 8eb6840..13ddebe 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/home/HomeFragment.java +++ b/Listify/app/src/main/java/com/example/listify/ui/home/HomeFragment.java @@ -70,9 +70,7 @@ public class HomeFragment extends Fragment { }); builder.setNegativeButton("No", new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { - return; - } + public void onClick(DialogInterface dialog, int which) {} }); AlertDialog dialog = builder.create(); dialog.show(); From 4eddf3ce2dccc9d29a5e8d901a1f56e783505881 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Fri, 23 Oct 2020 22:06:21 -0700 Subject: [PATCH 12/46] Can now divde the items in shopping list by store --- .../java/com/example/listify/ListPage.java | 88 ++++++++----------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index fa449d2..99cb518 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -17,6 +17,8 @@ import org.json.JSONException; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import static com.example.listify.MainActivity.am; @@ -37,6 +39,8 @@ public class ListPage extends AppCompatActivity { ArrayList pListItemPair = new ArrayList<>(); + Map numItemsFromStore = new HashMap<>(); + Requestor requestor; @Override @@ -52,7 +56,6 @@ public class ListPage extends AppCompatActivity { } requestor = new Requestor(am, configs.getProperty("apiKey")); SynchronousReceiver lr = new SynchronousReceiver<>(); - //ListReceiver lr = new ListReceiver<>(); requestor.getObject(Integer.toString(listID), List.class, lr); List list; @@ -77,34 +80,45 @@ public class ListPage extends AppCompatActivity { item = null; } if(item != null) { - pNames.add(item.getDescription()); - pStores.add("Kroger"); - pPrices.add(item.getPrice().toString()); - pQuantity.add(entry.getQuantity().toString()); - pImages.add(R.drawable.placeholder); - pListItemPair.add(entry); + if(!numItemsFromStore.containsKey("Kroger") || numItemsFromStore.get("Kroger") == 0) { + pNames.add("Kroger"); + pStores.add(""); + pPrices.add("$?.??"); + pQuantity.add("0"); + pImages.add(-1); + pListItemPair.add(null); + + numItemsFromStore.put("Kroger", 1); + + pNames.add(item.getDescription()); + pStores.add("Kroger"); + pPrices.add(item.getPrice().toString()); + pQuantity.add(entry.getQuantity().toString()); + pImages.add(R.drawable.placeholder); + pListItemPair.add(entry); + } + else { + int index = 0; + + while(index < pNames.size() && pNames.get(index).equals("Kroger")) { + index++; + } + + index++; + + pNames.add(index, item.getDescription()); + pStores.add(index, "Kroger"); + pPrices.add(index, item.getPrice().toString()); + pQuantity.add(index, entry.getQuantity().toString()); + pImages.add(index, R.drawable.placeholder); + pListItemPair.add(index, entry); + + numItemsFromStore.put("Kroger", numItemsFromStore.get("Kroger") + 1); + } } } } - /*pNames.add("Half-gallon organic whole milk"); - pStores.add("Kroger"); - pPrices.add("$5.00"); - pQuantity.add("1"); - pImages.add(R.drawable.milk); - - pNames.add("5-bunch medium bananas"); - pStores.add("Kroger"); - pPrices.add("$3.00"); - pQuantity.add("1"); - pImages.add(R.drawable.bananas); - - pNames.add("JIF 40-oz creamy peanut butter"); - pStores.add("Kroger"); - pPrices.add("$7.00"); - pQuantity.add("1"); - pImages.add(R.drawable.peanutbutter);*/ - super.onCreate(savedInstanceState); setContentView(R.layout.activity_list); @@ -232,28 +246,4 @@ public class ListPage extends AppCompatActivity { return listproduct; } } - - class ListReceiver implements Requestor.Receiver { - @Override - public void acceptDelivery(T delivered) { - for(ListEntry entry : ((List) delivered).getEntries()) { - int product = entry.getProductID(); - ProductReceiver pr = new ProductReceiver<>(); - requestor.getObject(Integer.toString(product), Item.class, pr); - pQuantity.add(entry.getQuantity().toString()); - pListItemPair.add(entry); - } - } - } - - class ProductReceiver implements Requestor.Receiver { - @Override - public void acceptDelivery(T delivered) { - Item i = (Item) delivered; - pNames.add(i.getDescription()); - pStores.add("Kroger"); - pPrices.add(i.getPrice().toString()); - pImages.add(R.drawable.placeholder); - } - } } From 45c7ac15bdbca51bbe8ce5e521a25731b30d8c9d Mon Sep 17 00:00:00 2001 From: NMerz Date: Sat, 24 Oct 2020 11:53:12 -0400 Subject: [PATCH 13/46] Limit item search results Add a limit on item search results so the user is not overwhelmed. --- .../Lists/ItemSearch/src/ItemSearcher.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Lambdas/Lists/ItemSearch/src/ItemSearcher.java b/Lambdas/Lists/ItemSearch/src/ItemSearcher.java index 29bc78c..a3a7381 100644 --- a/Lambdas/Lists/ItemSearch/src/ItemSearcher.java +++ b/Lambdas/Lists/ItemSearch/src/ItemSearcher.java @@ -7,26 +7,24 @@ import java.util.Map; public class ItemSearcher implements CallHandler { - DBConnector connector; + Connection connection; String cognitoID; - private final String GET_ITEM_MATCHES = "SELECT * FROM Product WHERE description LIKE ?"; + private final String GET_ITEM_MATCHES = "SELECT * FROM Product WHERE description LIKE ? LIMIT 100;"; - public ItemSearcher(DBConnector connector, String cognitoID) { - this.connector = connector; + public ItemSearcher(Connection connection, String cognitoID) { + this.connection = connection; this.cognitoID = cognitoID; } @Override public Object conductAction(Map body, HashMap queryParams, String s) throws SQLException { - try (Connection connection = connector.getConnection()) { - PreparedStatement getItemMatches = connection.prepareStatement(GET_ITEM_MATCHES); - getItemMatches.setString(1, "%" + queryParams.get("id") + "%"); - System.out.println(getItemMatches); - ResultSet searchResults = getItemMatches.executeQuery(); - ItemSearch searchResultsObject = new ItemSearch(searchResults); - System.out.println(searchResultsObject); - return searchResultsObject; - } + PreparedStatement getItemMatches = connection.prepareStatement(GET_ITEM_MATCHES); + getItemMatches.setString(1, "%" + queryParams.get("id") + "%"); + System.out.println(getItemMatches); + ResultSet searchResults = getItemMatches.executeQuery(); + ItemSearch searchResultsObject = new ItemSearch(searchResults); + System.out.println(searchResultsObject); + return searchResultsObject; } } From 2637cab2827a24867a547ab25a946034fe592744 Mon Sep 17 00:00:00 2001 From: NMerz Date: Sat, 24 Oct 2020 12:52:45 -0400 Subject: [PATCH 14/46] Create userID retrieval Lambda Allow for the retrieval of userIDs from email (or just generally). This may need the apigateway passthrough info modified for privacy protection --- Lambdas/Lists/User/src/UserGET.java | 11 +++++ Lambdas/Lists/User/src/UserGetter.java | 60 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 Lambdas/Lists/User/src/UserGET.java create mode 100644 Lambdas/Lists/User/src/UserGetter.java diff --git a/Lambdas/Lists/User/src/UserGET.java b/Lambdas/Lists/User/src/UserGET.java new file mode 100644 index 0000000..d9a7244 --- /dev/null +++ b/Lambdas/Lists/User/src/UserGET.java @@ -0,0 +1,11 @@ +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.util.Map; + +public class UserGET implements RequestHandler, Object> { + + public Object handleRequest(Map inputMap, Context unfilled) { + return BasicHandler.handleRequest(inputMap, unfilled, UserGetter.class); + } +} diff --git a/Lambdas/Lists/User/src/UserGetter.java b/Lambdas/Lists/User/src/UserGetter.java new file mode 100644 index 0000000..043a0c2 --- /dev/null +++ b/Lambdas/Lists/User/src/UserGetter.java @@ -0,0 +1,60 @@ +import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProvider; +import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProviderClientBuilder; +import com.amazonaws.services.cognitoidp.model.AttributeType; +import com.amazonaws.services.cognitoidp.model.ListUsersRequest; +import com.amazonaws.services.cognitoidp.model.ListUsersResult; +import com.amazonaws.services.cognitoidp.model.UserType; + +import java.io.IOException; +import java.sql.Connection; +import java.util.*; + +public class UserGetter implements CallHandler { + private String cognitoID; + + public UserGetter(Connection connection, String cognitoID) { + this.cognitoID = cognitoID; + } + @Override + public Object conductAction(Map bodyMap, HashMap queryMap, String cognitoID) { + Properties cognitoProperties; + try { + cognitoProperties = DBConnector.loadProperties("cognitoProperties.json"); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + String userPoolId = cognitoProperties.get("userPoolId").toString(); + System.out.println(userPoolId); + ListUsersRequest checkRequest = new ListUsersRequest().withUserPoolId(userPoolId); + Object emailObject = bodyMap.get("emailToCheck"); + if (emailObject != null) { + checkRequest.setFilter("email=\"" + emailObject.toString() +"\""); + } else { + // checkRequest.setFilter("sub=\"" + cognitoID + "\""); + return cognitoID; + } + System.out.println(checkRequest); + AWSCognitoIdentityProvider awsCognitoIdentityProvider = AWSCognitoIdentityProviderClientBuilder.defaultClient(); + ListUsersResult foundUsersResult = awsCognitoIdentityProvider.listUsers(checkRequest); + List foundUsers = foundUsersResult.getUsers(); + if (foundUsers.size() != 1) { + System.out.println(foundUsers); + if (foundUsers.size() == 0) { + throw new InputMismatchException("Not user with given email"); + } + throw new InputMismatchException("Found more than one user with supposedly unique email"); + } + UserType foundUser = foundUsers.get(0); + System.out.println(foundUser.getAttributes()); + String sub = ""; + for (AttributeType attribute : foundUser.getAttributes()) { + if (attribute.getName().equals("sub")) { + sub = attribute.getValue(); + break; + } + System.out.println(attribute.getName() + ": " + attribute.getValue()); + } + return sub; + } +} From 34d74aae6af1798d011731e652b2e689b815843f Mon Sep 17 00:00:00 2001 From: NMerz Date: Sat, 24 Oct 2020 14:46:56 -0400 Subject: [PATCH 15/46] Treat ListSharee as access table This is so that multiple users can be paired with a single list. In the future, we may want to reconsider list deletion behavior to simply remove a user's access and only delete it when no one has access. We may also want the user deletion Lambda to use the list deletion Lambda when it is created. --- Lambdas/Lists/List/src/ListAdder.java | 13 +++- Lambdas/Lists/List/src/ListGetter.java | 2 +- .../Lists/ListShare/src/ListSharePOST.java | 11 +++ Lambdas/Lists/ListShare/src/ListSharer.java | 68 +++++++++++++++++++ Lambdas/Lists/User/src/UserDeleter.java | 16 ++++- Lambdas/Lists/pom.xml | 9 ++- 6 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 Lambdas/Lists/ListShare/src/ListSharePOST.java create mode 100644 Lambdas/Lists/ListShare/src/ListSharer.java diff --git a/Lambdas/Lists/List/src/ListAdder.java b/Lambdas/Lists/List/src/ListAdder.java index a65d780..03fd809 100644 --- a/Lambdas/Lists/List/src/ListAdder.java +++ b/Lambdas/Lists/List/src/ListAdder.java @@ -8,7 +8,8 @@ public class ListAdder implements CallHandler { private Connection connection; private String cognitoID; - private final String LIST_CREATE = "INSERT INTO List (name, owner, lastUpdated) VALUES (?, ?, ?)"; + private final String LIST_CREATE = "INSERT INTO List (name, owner, lastUpdated) VALUES (?, ?, ?);"; + private final String LIST_ACCESS_GRANT = "INSERT INTO ListSharee(listID, userID) VALUES(?, ?);"; public ListAdder(Connection connection, String cognitoID) { this.connection = connection; @@ -17,7 +18,9 @@ public class ListAdder implements CallHandler { public Object conductAction(Map bodyMap, HashMap queryString, String cognitoID) throws SQLException { PreparedStatement statement = connection.prepareStatement(LIST_CREATE, Statement.RETURN_GENERATED_KEYS); - statement.setString(1, bodyMap.get("name").toString());//Needs safe checking + + String listName = bodyMap.get("name").toString();//Needs safe checking + statement.setString(1, listName); statement.setString(2, cognitoID); statement.setTimestamp(3, Timestamp.from(Instant.now())); System.out.println(statement); @@ -25,7 +28,13 @@ public class ListAdder implements CallHandler { ResultSet newIDRS = statement.getGeneratedKeys(); newIDRS.first(); Integer newID = newIDRS.getInt(1); + PreparedStatement accessGrant = connection.prepareStatement(LIST_ACCESS_GRANT); + accessGrant.setInt(1, newID); + accessGrant.setString(2, cognitoID); + System.out.println(accessGrant); + accessGrant.executeUpdate(); connection.commit(); + System.out.println(newID); return newID; } } diff --git a/Lambdas/Lists/List/src/ListGetter.java b/Lambdas/Lists/List/src/ListGetter.java index 0ad47f9..c5a9ed1 100644 --- a/Lambdas/Lists/List/src/ListGetter.java +++ b/Lambdas/Lists/List/src/ListGetter.java @@ -11,7 +11,7 @@ public class ListGetter implements CallHandler{ private final String cognitoID; private final String GET_LIST = "SELECT * FROM List WHERE listID = ?;"; - private final String GET_LISTS = "SELECT listID FROM List WHERE owner = ?;"; + private final String GET_LISTS = "SELECT listID FROM ListSharee WHERE userID = ?;"; private final String GET_ENTRIES = "SELECT * FROM ListProduct WHERE listID = ?;"; public ListGetter(Connection connection, String cognitoID) { diff --git a/Lambdas/Lists/ListShare/src/ListSharePOST.java b/Lambdas/Lists/ListShare/src/ListSharePOST.java new file mode 100644 index 0000000..c986edd --- /dev/null +++ b/Lambdas/Lists/ListShare/src/ListSharePOST.java @@ -0,0 +1,11 @@ +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.util.Map; + +public class ListSharePOST implements RequestHandler, Object> { + + public Object handleRequest(Map inputMap, Context unfilled) { + return BasicHandler.handleRequest(inputMap, unfilled, ListSharer.class); + } +} diff --git a/Lambdas/Lists/ListShare/src/ListSharer.java b/Lambdas/Lists/ListShare/src/ListSharer.java new file mode 100644 index 0000000..6b15c3b --- /dev/null +++ b/Lambdas/Lists/ListShare/src/ListSharer.java @@ -0,0 +1,68 @@ +import com.amazonaws.services.lambda.AWSLambdaClientBuilder; +import com.amazonaws.services.lambda.model.InvokeRequest; +import com.amazonaws.services.lambda.model.InvokeResult; + +import java.security.AccessControlException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.InputMismatchException; +import java.util.Map; + +public class ListSharer implements CallHandler { + + private Connection connection; + private String cognitoID; + + public ListSharer(Connection connection, String cognitoID) { + this.connection = connection; + this.cognitoID = cognitoID; + } + + final private String CHECK_ACCESS = "SELECT * from ListSharee WHERE listID = ? AND userID = ?;"; + final private String SHARE_LIST = "INSERT INTO ListSharee(listID, userID) VALUES(?, ?);"; + + public Object conductAction(Map bodyMap, HashMap queryString, String cognitoID) throws SQLException { + PreparedStatement checkAccess = connection.prepareStatement(CHECK_ACCESS); + Integer listID = Integer.parseInt(bodyMap.get("listID").toString()); + checkAccess.setInt(1, listID); + checkAccess.setString(2, cognitoID); + ResultSet checkAccessRS = checkAccess.executeQuery(); + if (!checkAccessRS.next()) { + throw new AccessControlException("The requesting user does not have access to the requested list"); + } + InvokeRequest invokeRequest = new InvokeRequest(); + invokeRequest.setFunctionName("UserGET"); + invokeRequest.setPayload("{" + + " \"body\": {" + + " \"emailToCheck\": \"" + bodyMap.get("shareWith").toString() + "\"" + + " }," + + " \"params\": {" + + " \"querystring\": {" + + " }" + + " }," + + " \"context\": {" + + " \"sub\": \"not used\"" + + " }" + + "}"); + InvokeResult invokeResult = AWSLambdaClientBuilder.defaultClient().invoke(invokeRequest); + if (invokeResult.getStatusCode() != 200) { + throw new InputMismatchException("Could not find specified user to share with"); + } + String shareWithSub = new String(invokeResult.getPayload().array()).replace("\"", ""); + checkAccess.setString(2, shareWithSub); + checkAccessRS = checkAccess.executeQuery(); + if (checkAccessRS.next()) { + throw new InputMismatchException("The specified user already has access"); + } + + PreparedStatement shareList = connection.prepareStatement(SHARE_LIST); + shareList.setInt(1, listID); + shareList.setString(2, shareWithSub); + shareList.executeUpdate(); + connection.commit(); + return null; + } +} diff --git a/Lambdas/Lists/User/src/UserDeleter.java b/Lambdas/Lists/User/src/UserDeleter.java index b440d9e..e89776b 100644 --- a/Lambdas/Lists/User/src/UserDeleter.java +++ b/Lambdas/Lists/User/src/UserDeleter.java @@ -19,6 +19,8 @@ public class UserDeleter implements CallHandler { private final String GET_LISTS = "SELECT * FROM List WHERE (owner = ?);"; private final String DELETE_LIST_PRODUCT = "DELETE FROM ListProduct WHERE (listID = ?);"; private final String DELETE_LISTS = "DELETE FROM List WHERE (owner = ?);"; + private final String DELETE_LIST_SHARES = "DELETE FROM ListSharee WHERE (listID = ?);"; + private final String DELETE_LIST_ACCESS = "DELETE FROM ListSharee WHERE (userID = ?);"; public UserDeleter(Connection connection, String cognitoID) { this.connection = connection; @@ -57,13 +59,23 @@ public class UserDeleter implements CallHandler { statement = connection.prepareStatement(DELETE_LIST_PRODUCT); statement.setInt(1, listID); System.out.println(statement); - statement.executeQuery(); + statement.executeUpdate(); + + statement = connection.prepareStatement(DELETE_LIST_SHARES); + statement.setInt(1, listID); + System.out.println(statement); + statement.executeUpdate(); } statement = connection.prepareStatement(DELETE_LISTS); statement.setString(1, cognitoID); System.out.println(statement); - statement.executeQuery(); + statement.executeUpdate(); + statement = connection.prepareStatement(DELETE_LIST_ACCESS); + statement.setString(1, cognitoID); + System.out.println(statement); + statement.executeUpdate(); + connection.commit(); return null; diff --git a/Lambdas/Lists/pom.xml b/Lambdas/Lists/pom.xml index c80c2c7..6fbc972 100644 --- a/Lambdas/Lists/pom.xml +++ b/Lambdas/Lists/pom.xml @@ -14,10 +14,15 @@ aws-lambda-java-core 1.2.1 + + com.amazonaws + aws-java-sdk-lambda + 1.11.875 + com.amazonaws aws-lambda-java-events - 3.1.0 + 3.4.0 com.amazonaws @@ -42,7 +47,7 @@ org.apache.httpcomponents httpclient - 4.5.12 + 4.5.13 com.google.code.gson From f2346f32a9a5891edbb9faf8d4c748ea94e476bf Mon Sep 17 00:00:00 2001 From: NMerz Date: Sat, 24 Oct 2020 14:50:11 -0400 Subject: [PATCH 16/46] Add client-side shareWithEmail Define the contract for sharing lists. To share, simply post an instance of ListShare. --- .../com/example/listify/data/ListShare.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Listify/app/src/main/java/com/example/listify/data/ListShare.java diff --git a/Listify/app/src/main/java/com/example/listify/data/ListShare.java b/Listify/app/src/main/java/com/example/listify/data/ListShare.java new file mode 100644 index 0000000..406f286 --- /dev/null +++ b/Listify/app/src/main/java/com/example/listify/data/ListShare.java @@ -0,0 +1,27 @@ +package com.example.listify.data; + +public class ListShare { + Integer listID; + String shareWithEmail; + + public ListShare(Integer listID, String shareWithEmail) { + this.listID = listID; + this.shareWithEmail = shareWithEmail; + } + + public Integer getListID() { + return listID; + } + + public void setListID(Integer listID) { + this.listID = listID; + } + + public String getShareWithEmail() { + return shareWithEmail; + } + + public void setShareWithEmail(String shareWithEmail) { + this.shareWithEmail = shareWithEmail; + } +} From 6fe3110c294f2db628231c71f2c6e79f194f4412 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Sat, 24 Oct 2020 12:40:28 -0700 Subject: [PATCH 17/46] Can now display the total price of the items from each store in shopping list --- .../java/com/example/listify/ListPage.java | 42 ++++++++++++++----- .../main/res/menu/activity_main_drawer.xml | 5 --- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index 99cb518..a5b9204 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -39,7 +39,7 @@ public class ListPage extends AppCompatActivity { ArrayList pListItemPair = new ArrayList<>(); - Map numItemsFromStore = new HashMap<>(); + Map totalPriceByStore = new HashMap<>(); Requestor requestor; @@ -80,16 +80,16 @@ public class ListPage extends AppCompatActivity { item = null; } if(item != null) { - if(!numItemsFromStore.containsKey("Kroger") || numItemsFromStore.get("Kroger") == 0) { + if(!totalPriceByStore.containsKey("Kroger")) { + totalPriceByStore.put("Kroger", item.getPrice().doubleValue() * entry.getQuantity()); + pNames.add("Kroger"); pStores.add(""); - pPrices.add("$?.??"); - pQuantity.add("0"); + pPrices.add("$" + totalPriceByStore.get("Kroger")); + pQuantity.add("-1"); pImages.add(-1); pListItemPair.add(null); - numItemsFromStore.put("Kroger", 1); - pNames.add(item.getDescription()); pStores.add("Kroger"); pPrices.add(item.getPrice().toString()); @@ -100,10 +100,13 @@ public class ListPage extends AppCompatActivity { else { int index = 0; - while(index < pNames.size() && pNames.get(index).equals("Kroger")) { + while(index < pNames.size() && !pNames.get(index).equals("Kroger")) { index++; } + totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") + (item.getPrice().doubleValue() * entry.getQuantity())); + pPrices.set(index, "$" + totalPriceByStore.get("Kroger")); + index++; pNames.add(index, item.getDescription()); @@ -112,8 +115,6 @@ public class ListPage extends AppCompatActivity { pQuantity.add(index, entry.getQuantity().toString()); pImages.add(index, R.drawable.placeholder); pListItemPair.add(index, entry); - - numItemsFromStore.put("Kroger", numItemsFromStore.get("Kroger") + 1); } } } @@ -161,6 +162,7 @@ public class ListPage extends AppCompatActivity { public void onClick(View v) { int q = Integer.parseInt(pQuantity.get(position)) - 1; pQuantity.set(position, Integer.toString(q)); + totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) - 1.0); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() - 1); pListItemPair.add(position, le); @@ -190,6 +192,7 @@ public class ListPage extends AppCompatActivity { public void onClick(View v) { int q = Integer.parseInt(pQuantity.get(position)) + 1; pQuantity.set(position, Integer.toString(q)); + totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) + 1.0); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() + 1); pListItemPair.add(position, le); @@ -217,6 +220,8 @@ public class ListPage extends AppCompatActivity { removeItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (1.0 * Integer.parseInt(pQuantity.get(position)))); + pNames.remove(position); pStores.remove(position); pPrices.remove(position); @@ -239,8 +244,23 @@ public class ListPage extends AppCompatActivity { name.setText(pNames.get(position)); store.setText(pStores.get(position)); price.setText(pPrices.get(position)); - quantity.setText(pQuantity.get(position)); - image.setImageResource(pImages.get(position)); + + if(pQuantity.get(position).equals("-1")) { + quantity.setVisibility(View.INVISIBLE); + decrQuan.setVisibility(View.INVISIBLE); + incrQuan.setVisibility(View.INVISIBLE); + removeItem.setVisibility(View.INVISIBLE); + } + else { + quantity.setText(pQuantity.get(position)); + } + + if(pImages.get(position) == -1) { + image.setVisibility(View.INVISIBLE); + } + else { + image.setImageResource(pImages.get(position)); + } } return listproduct; diff --git a/Listify/app/src/main/res/menu/activity_main_drawer.xml b/Listify/app/src/main/res/menu/activity_main_drawer.xml index 26a2072..b4aec58 100644 --- a/Listify/app/src/main/res/menu/activity_main_drawer.xml +++ b/Listify/app/src/main/res/menu/activity_main_drawer.xml @@ -20,11 +20,6 @@ android:id="@+id/nav_lists" android:icon="@drawable/ic_baseline_list_alt_28" android:title="@string/menu_lists" /> - - - - - Date: Sat, 24 Oct 2020 16:14:17 -0700 Subject: [PATCH 18/46] Total prices change on the spot after changing quantity and removing items --- .../java/com/example/listify/ListPage.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index a5b9204..a45e323 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -40,6 +40,7 @@ public class ListPage extends AppCompatActivity { ArrayList pListItemPair = new ArrayList<>(); Map totalPriceByStore = new HashMap<>(); + Map storeHeaderIndex = new HashMap<>(); Requestor requestor; @@ -82,10 +83,11 @@ public class ListPage extends AppCompatActivity { if(item != null) { if(!totalPriceByStore.containsKey("Kroger")) { totalPriceByStore.put("Kroger", item.getPrice().doubleValue() * entry.getQuantity()); + storeHeaderIndex.put("Kroger", pNames.size()); pNames.add("Kroger"); pStores.add(""); - pPrices.add("$" + totalPriceByStore.get("Kroger")); + pPrices.add(totalPriceByStore.get("Kroger").toString()); pQuantity.add("-1"); pImages.add(-1); pListItemPair.add(null); @@ -105,7 +107,7 @@ public class ListPage extends AppCompatActivity { } totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") + (item.getPrice().doubleValue() * entry.getQuantity())); - pPrices.set(index, "$" + totalPriceByStore.get("Kroger")); + pPrices.set(index, totalPriceByStore.get("Kroger").toString()); index++; @@ -115,6 +117,12 @@ public class ListPage extends AppCompatActivity { pQuantity.add(index, entry.getQuantity().toString()); pImages.add(index, R.drawable.placeholder); pListItemPair.add(index, entry); + + for(String store : storeHeaderIndex.keySet()) { + if(storeHeaderIndex.get(store) > index) { + storeHeaderIndex.put(store, storeHeaderIndex.get(store) + 1); + } + } } } } @@ -162,7 +170,8 @@ public class ListPage extends AppCompatActivity { public void onClick(View v) { int q = Integer.parseInt(pQuantity.get(position)) - 1; pQuantity.set(position, Integer.toString(q)); - totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) - 1.0); + totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) - Double.parseDouble(pPrices.get(position))); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() - 1); pListItemPair.add(position, le); @@ -192,7 +201,8 @@ public class ListPage extends AppCompatActivity { public void onClick(View v) { int q = Integer.parseInt(pQuantity.get(position)) + 1; pQuantity.set(position, Integer.toString(q)); - totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) + 1.0); + totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) + Double.parseDouble(pPrices.get(position))); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() + 1); pListItemPair.add(position, le); @@ -220,7 +230,8 @@ public class ListPage extends AppCompatActivity { removeItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (1.0 * Integer.parseInt(pQuantity.get(position)))); + totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); pNames.remove(position); pStores.remove(position); @@ -243,7 +254,7 @@ public class ListPage extends AppCompatActivity { if(!pNames.isEmpty()) { name.setText(pNames.get(position)); store.setText(pStores.get(position)); - price.setText(pPrices.get(position)); + price.setText("$" + pPrices.get(position)); if(pQuantity.get(position).equals("-1")) { quantity.setVisibility(View.INVISIBLE); From c07599ecc6b2417a4ed3480ea21c52204ba84f4f Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sun, 25 Oct 2020 16:53:51 -0400 Subject: [PATCH 19/46] Add threads in awaited for loops. Remove new threads from Requestor.getObject calls --- .../java/com/example/listify/ItemDetails.java | 40 ++++++++++++++--- .../com/example/listify/SearchResults.java | 9 +--- .../listify/ui/lists/ListsFragment.java | 44 ++++++++++++------- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ItemDetails.java b/Listify/app/src/main/java/com/example/listify/ItemDetails.java index e76f699..89fe980 100644 --- a/Listify/app/src/main/java/com/example/listify/ItemDetails.java +++ b/Listify/app/src/main/java/com/example/listify/ItemDetails.java @@ -88,22 +88,50 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); SynchronousReceiver listIdsReceiver = new SynchronousReceiver<>(); - SynchronousReceiver listReceiver = new SynchronousReceiver<>(); requestor.getListOfIds(List.class, listIdsReceiver, listIdsReceiver); Thread t = new Thread(new Runnable() { @Override public void run() { + Integer[] listIds = null; try { - Integer[] listIds = listIdsReceiver.await(); - for (int i = 0; i < listIds.length; i++) { - requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); - shoppingLists.add(listReceiver.await()); - } + listIds = listIdsReceiver.await(); } catch (Exception e) { e.printStackTrace(); } + // Create threads and add them to a list + Thread[] threads = new Thread[listIds.length]; + List[] results = new List[listIds.length]; + for (int i = 0; i < listIds.length; i++) { + SynchronousReceiver listReceiver = new SynchronousReceiver<>(); + requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); + int finalI = i; + Thread l = new Thread(new Runnable() { + @Override + public void run() { + try { + results[finalI] = listReceiver.await(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + threads[i] = l; + l.start(); + } + + // Wait for each thread to finish and add results to shoppingLists + for (int i = 0; i < threads.length; i++) { + try { + threads[i].join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + shoppingLists.add(results[i]); + } + + loadingDialog.cancel(); ListPickerDialogFragment listPickerDialog = new ListPickerDialogFragment(shoppingLists); listPickerDialog.show(getSupportFragmentManager(), "User Lists"); diff --git a/Listify/app/src/main/java/com/example/listify/SearchResults.java b/Listify/app/src/main/java/com/example/listify/SearchResults.java index 6b62c74..18e4671 100644 --- a/Listify/app/src/main/java/com/example/listify/SearchResults.java +++ b/Listify/app/src/main/java/com/example/listify/SearchResults.java @@ -108,14 +108,7 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme // Clear old search results from the view resultsProductListSorted.clear(); searchResultsListAdapter.notifyDataSetChanged(); - - Thread t = new Thread(new Runnable() { - @Override - public void run() { - doSearch(query); - } - }); - t.start(); + doSearch(query); return false; } diff --git a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java index 0e1ccbf..cec4674 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java +++ b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java @@ -66,13 +66,7 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. SynchronousReceiver listIdsReceiver = new SynchronousReceiver<>(); final Requestor.Receiver recv = this; - Thread t = new Thread(new Runnable() { - @Override - public void run() { - requestor.getListOfIds(List.class, recv, null); - } - }); - t.start(); + requestor.getListOfIds(List.class, recv, null); FloatingActionButton fab = (FloatingActionButton) root.findViewById(R.id.new_list_fab); @@ -144,16 +138,36 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. @Override public void acceptDelivery(Object delivered) { - SynchronousReceiver listReceiver = new SynchronousReceiver<>(); Integer[] listIds = (Integer[]) delivered; - try { -// Integer[] listIds = listIdsReceiver.await(); - for (int i = 0; i < listIds.length; i++) { - requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); - shoppingLists.add(listReceiver.await()); + // Create threads and add them to a list + Thread[] threads = new Thread[listIds.length]; + List[] results = new List[listIds.length]; + for (int i = 0; i < listIds.length; i++) { + SynchronousReceiver listReceiver = new SynchronousReceiver<>(); + requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); + int finalI = i; + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + results[finalI] = listReceiver.await(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + threads[i] = t; + t.start(); + } + + // Wait for each thread to finish and add results to shoppingLists + for (int i = 0; i < threads.length; i++) { + try { + threads[i].join(); + } catch (InterruptedException e) { + e.printStackTrace(); } - } catch (Exception e) { - e.printStackTrace(); + shoppingLists.add(results[i]); } // Set adapter and display this users lists From 7bc43d82499f2e92619e62942bc75c225e90e40a Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Sun, 25 Oct 2020 16:15:42 -0700 Subject: [PATCH 20/46] Simplified a section of code for total price by store (used HashMap to access header section) --- Listify/app/src/main/java/com/example/listify/ListPage.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index a45e323..a7a3e89 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -100,11 +100,7 @@ public class ListPage extends AppCompatActivity { pListItemPair.add(entry); } else { - int index = 0; - - while(index < pNames.size() && !pNames.get(index).equals("Kroger")) { - index++; - } + int index = storeHeaderIndex.get("Kroger"); totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") + (item.getPrice().doubleValue() * entry.getQuantity())); pPrices.set(index, totalPriceByStore.get("Kroger").toString()); From 07db1c833ad446fa1babb580581fd0fa53b92736 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sun, 25 Oct 2020 21:57:57 -0400 Subject: [PATCH 21/46] Fix duplicate lists displaying --- Listify/app/src/main/java/com/example/listify/ItemDetails.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Listify/app/src/main/java/com/example/listify/ItemDetails.java b/Listify/app/src/main/java/com/example/listify/ItemDetails.java index 89fe980..f2fb43c 100644 --- a/Listify/app/src/main/java/com/example/listify/ItemDetails.java +++ b/Listify/app/src/main/java/com/example/listify/ItemDetails.java @@ -121,6 +121,7 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr l.start(); } + shoppingLists.clear(); // Wait for each thread to finish and add results to shoppingLists for (int i = 0; i < threads.length; i++) { try { From caf929a71e2569e267be5d5603a96bdec29b0c44 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sun, 25 Oct 2020 22:52:35 -0400 Subject: [PATCH 22/46] Floating total price bar --- .../java/com/example/listify/ListPage.java | 8 +++++ .../com/example/listify/SearchResults.java | 1 + .../app/src/main/res/layout/activity_list.xml | 32 ++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index fa449d2..8558e28 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -16,6 +16,7 @@ import com.example.listify.data.ListEntry; import org.json.JSONException; import java.io.IOException; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Properties; @@ -64,6 +65,7 @@ public class ListPage extends AppCompatActivity { list = null; } + double totalPrice = 0; if(list != null) { for (ListEntry entry : list.getEntries()) { int product = entry.getProductID(); @@ -83,6 +85,9 @@ public class ListPage extends AppCompatActivity { pQuantity.add(entry.getQuantity().toString()); pImages.add(R.drawable.placeholder); pListItemPair.add(entry); + + // Increment total price + totalPrice += (item.getPrice().doubleValue() * entry.getQuantity()); } } } @@ -112,6 +117,9 @@ public class ListPage extends AppCompatActivity { myAdapter = new MyAdapter(this, pNames, pStores, pPrices, pQuantity, pImages); listView.setAdapter(myAdapter); + + TextView tvTotalPrice = (TextView) findViewById(R.id.total_price); + tvTotalPrice.setText(String.format("$%.2f", totalPrice)); } class MyAdapter extends ArrayAdapter { diff --git a/Listify/app/src/main/java/com/example/listify/SearchResults.java b/Listify/app/src/main/java/com/example/listify/SearchResults.java index 0b312c6..d799cca 100644 --- a/Listify/app/src/main/java/com/example/listify/SearchResults.java +++ b/Listify/app/src/main/java/com/example/listify/SearchResults.java @@ -176,6 +176,7 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme sortResults(); } + // TODO: Scroll the list back to the top when a search, sort, or filter is performed // Sorts the search results private void sortResults() { // Reset the filtered list diff --git a/Listify/app/src/main/res/layout/activity_list.xml b/Listify/app/src/main/res/layout/activity_list.xml index 65c12f4..12e134b 100644 --- a/Listify/app/src/main/res/layout/activity_list.xml +++ b/Listify/app/src/main/res/layout/activity_list.xml @@ -1,5 +1,5 @@ - @@ -7,8 +7,32 @@ - + android:id="@+id/listView" + android:paddingBottom="20dp"> - \ No newline at end of file + + + + + + + + + \ No newline at end of file From 04a944f2d5a4dbcfcd6125a3004995f79d29f27d Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sun, 25 Oct 2020 23:06:56 -0400 Subject: [PATCH 23/46] Update total price when quantities change and items are removed --- .../java/com/example/listify/ListPage.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index 8558e28..580bb65 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -16,7 +16,6 @@ import com.example.listify.data.ListEntry; import org.json.JSONException; import java.io.IOException; -import java.math.BigDecimal; import java.util.ArrayList; import java.util.Properties; @@ -29,6 +28,7 @@ public class ListPage extends AppCompatActivity { Button incrQuan; Button decrQuan; Button removeItem; + TextView tvTotalPrice; ArrayList pNames = new ArrayList<>(); ArrayList pStores = new ArrayList<>(); @@ -39,6 +39,7 @@ public class ListPage extends AppCompatActivity { ArrayList pListItemPair = new ArrayList<>(); Requestor requestor; + double totalPrice = 0; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -65,7 +66,6 @@ public class ListPage extends AppCompatActivity { list = null; } - double totalPrice = 0; if(list != null) { for (ListEntry entry : list.getEntries()) { int product = entry.getProductID(); @@ -118,7 +118,7 @@ public class ListPage extends AppCompatActivity { listView.setAdapter(myAdapter); - TextView tvTotalPrice = (TextView) findViewById(R.id.total_price); + tvTotalPrice = (TextView) findViewById(R.id.total_price); tvTotalPrice.setText(String.format("$%.2f", totalPrice)); } @@ -158,6 +158,10 @@ public class ListPage extends AppCompatActivity { ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() - 1); pListItemPair.add(position, le); + + totalPrice -= Double.parseDouble(pPrices.get(position)); + tvTotalPrice.setText(String.format("$%.2f", totalPrice)); + SynchronousReceiver synchronousenforcer = new SynchronousReceiver<>(); requestor.deleteObject(le, synchronousenforcer, synchronousenforcer); try { @@ -187,6 +191,10 @@ public class ListPage extends AppCompatActivity { ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() + 1); pListItemPair.add(position, le); + + totalPrice += Double.parseDouble(pPrices.get(position)); + tvTotalPrice.setText(String.format("$%.2f", totalPrice)); + SynchronousReceiver synchronousenforcer = new SynchronousReceiver<>(); requestor.deleteObject(le, synchronousenforcer, synchronousenforcer); try { @@ -211,6 +219,10 @@ public class ListPage extends AppCompatActivity { removeItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + totalPrice -= (Double.parseDouble(pPrices.get(position)) * + Double.parseDouble(pQuantity.get(position))); + tvTotalPrice.setText(String.format("$%.2f", totalPrice)); + pNames.remove(position); pStores.remove(position); pPrices.remove(position); From a3e72d3699baeb26b538f50b4af5f0a34029ae5a Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Sun, 25 Oct 2020 21:47:48 -0700 Subject: [PATCH 24/46] Shopping list can now display actual image of item --- .../java/com/example/listify/ListPage.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index a7a3e89..2a1fbd6 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -10,10 +10,12 @@ import android.widget.*; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; + +import com.bumptech.glide.Glide; + import com.example.listify.data.Item; import com.example.listify.data.List; import com.example.listify.data.ListEntry; -import org.json.JSONException; import java.io.IOException; import java.util.ArrayList; @@ -21,6 +23,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import org.json.JSONException; + import static com.example.listify.MainActivity.am; public class ListPage extends AppCompatActivity { @@ -35,7 +39,7 @@ public class ListPage extends AppCompatActivity { ArrayList pStores = new ArrayList<>(); ArrayList pPrices = new ArrayList<>(); ArrayList pQuantity = new ArrayList<>(); - ArrayList pImages = new ArrayList<>(); + ArrayList pImages = new ArrayList<>(); ArrayList pListItemPair = new ArrayList<>(); @@ -89,14 +93,14 @@ public class ListPage extends AppCompatActivity { pStores.add(""); pPrices.add(totalPriceByStore.get("Kroger").toString()); pQuantity.add("-1"); - pImages.add(-1); + pImages.add("-1"); pListItemPair.add(null); pNames.add(item.getDescription()); pStores.add("Kroger"); pPrices.add(item.getPrice().toString()); pQuantity.add(entry.getQuantity().toString()); - pImages.add(R.drawable.placeholder); + pImages.add(item.getImageURL()); pListItemPair.add(entry); } else { @@ -111,7 +115,7 @@ public class ListPage extends AppCompatActivity { pStores.add(index, "Kroger"); pPrices.add(index, item.getPrice().toString()); pQuantity.add(index, entry.getQuantity().toString()); - pImages.add(index, R.drawable.placeholder); + pImages.add(index, item.getImageURL()); pListItemPair.add(index, entry); for(String store : storeHeaderIndex.keySet()) { @@ -139,9 +143,9 @@ public class ListPage extends AppCompatActivity { ArrayList pStores; ArrayList pPrices; ArrayList pQuantity; - ArrayList pImages; + ArrayList pImages; - MyAdapter (Context c, ArrayList names, ArrayList stores, ArrayList prices, ArrayList quantity, ArrayList images) { + MyAdapter (Context c, ArrayList names, ArrayList stores, ArrayList prices, ArrayList quantity, ArrayList images) { super(c, R.layout.activity_listproductentry, R.id.productView, names); context = c; pNames = names; @@ -262,11 +266,11 @@ public class ListPage extends AppCompatActivity { quantity.setText(pQuantity.get(position)); } - if(pImages.get(position) == -1) { + if(pImages.get(position).equals("-1")) { image.setVisibility(View.INVISIBLE); } else { - image.setImageResource(pImages.get(position)); + Glide.with(getContext()).load(pImages.get(position)).into(image); } } From 0562c66dbb90cab9f3d63b5ee5c7a6217d35afa6 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Sun, 25 Oct 2020 22:15:27 -0700 Subject: [PATCH 25/46] Can now display the grand total of items in shopping list --- .../java/com/example/listify/ListPage.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index 2a1fbd6..e4c057b 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -53,6 +53,13 @@ public class ListPage extends AppCompatActivity { // Read list ID from caller final int listID = (int) getIntent().getSerializableExtra("listID"); + pNames.add("Total Price"); + pStores.add(""); + pPrices.add("0.00"); + pQuantity.add("-1"); + pImages.add("-1"); + pListItemPair.add(null); + Properties configs = new Properties(); try { configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json"); @@ -89,6 +96,9 @@ public class ListPage extends AppCompatActivity { totalPriceByStore.put("Kroger", item.getPrice().doubleValue() * entry.getQuantity()); storeHeaderIndex.put("Kroger", pNames.size()); + double newTotal = Double.parseDouble(pPrices.get(0)) + (item.getPrice().doubleValue() * entry.getQuantity()); + pPrices.set(0, String.valueOf(newTotal)); + pNames.add("Kroger"); pStores.add(""); pPrices.add(totalPriceByStore.get("Kroger").toString()); @@ -109,6 +119,9 @@ public class ListPage extends AppCompatActivity { totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") + (item.getPrice().doubleValue() * entry.getQuantity())); pPrices.set(index, totalPriceByStore.get("Kroger").toString()); + double newTotal = Double.parseDouble(pPrices.get(0)) + (item.getPrice().doubleValue() * entry.getQuantity()); + pPrices.set(0, String.valueOf(newTotal)); + index++; pNames.add(index, item.getDescription()); @@ -172,6 +185,8 @@ public class ListPage extends AppCompatActivity { pQuantity.set(position, Integer.toString(q)); totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) - Double.parseDouble(pPrices.get(position))); pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); + double newTotal = Double.parseDouble(pPrices.get(0)) - Double.parseDouble(pPrices.get(position)); + pPrices.set(0, String.valueOf(newTotal)); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() - 1); pListItemPair.add(position, le); @@ -203,6 +218,8 @@ public class ListPage extends AppCompatActivity { pQuantity.set(position, Integer.toString(q)); totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) + Double.parseDouble(pPrices.get(position))); pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); + double newTotal = Double.parseDouble(pPrices.get(0)) + Double.parseDouble(pPrices.get(position)); + pPrices.set(0, String.valueOf(newTotal)); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() + 1); pListItemPair.add(position, le); @@ -233,6 +250,9 @@ public class ListPage extends AppCompatActivity { totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); + double newTotal = Double.parseDouble(pPrices.get(0)) - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position))); + pPrices.set(0, String.valueOf(newTotal)); + pNames.remove(position); pStores.remove(position); pPrices.remove(position); From 049d95bb914c02aa31ba0ae48330e895618c5d5c Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Sun, 25 Oct 2020 22:39:56 -0700 Subject: [PATCH 26/46] Can now clear shopping list (i.e. remove all items) --- .../java/com/example/listify/ListPage.java | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index e4c057b..2e9dffa 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -247,19 +247,41 @@ public class ListPage extends AppCompatActivity { removeItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); - pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); + if(position == 0) { + pNames.clear(); + pStores.clear(); + pPrices.clear(); + pQuantity.clear(); + pImages.clear(); - double newTotal = Double.parseDouble(pPrices.get(0)) - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position))); - pPrices.set(0, String.valueOf(newTotal)); + pNames.add("Total Price"); + pStores.add(""); + pPrices.add("0.00"); + pQuantity.add("-1"); + pImages.add("-1"); - pNames.remove(position); - pStores.remove(position); - pPrices.remove(position); - pQuantity.remove(position); - pImages.remove(position); + while(pListItemPair.size() > 1) { + try { + requestor.deleteObject(pListItemPair.remove(1)); + } + catch(Exception e) {} + } + } + else { + totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); - requestor.deleteObject(pListItemPair.remove(position)); + double newTotal = Double.parseDouble(pPrices.get(0)) - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position))); + pPrices.set(0, String.valueOf(newTotal)); + + pNames.remove(position); + pStores.remove(position); + pPrices.remove(position); + pQuantity.remove(position); + pImages.remove(position); + + requestor.deleteObject(pListItemPair.remove(position)); + } listView.setAdapter(myAdapter); } @@ -280,7 +302,13 @@ public class ListPage extends AppCompatActivity { quantity.setVisibility(View.INVISIBLE); decrQuan.setVisibility(View.INVISIBLE); incrQuan.setVisibility(View.INVISIBLE); - removeItem.setVisibility(View.INVISIBLE); + + if(position == 0) { + + } + else { + removeItem.setVisibility(View.INVISIBLE); + } } else { quantity.setText(pQuantity.get(position)); From 2c73e8b224ece6d2a659cc4e28109307b86ddc2d Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Sun, 25 Oct 2020 23:27:25 -0700 Subject: [PATCH 27/46] Properly formatted the price values in shopping list --- .../java/com/example/listify/ListPage.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index 2e9dffa..6f3044f 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -18,6 +18,7 @@ import com.example.listify.data.List; import com.example.listify.data.ListEntry; import java.io.IOException; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -30,6 +31,7 @@ import static com.example.listify.MainActivity.am; public class ListPage extends AppCompatActivity { ListView listView; MyAdapter myAdapter; + Requestor requestor; Button incrQuan; Button decrQuan; @@ -46,7 +48,7 @@ public class ListPage extends AppCompatActivity { Map totalPriceByStore = new HashMap<>(); Map storeHeaderIndex = new HashMap<>(); - Requestor requestor; + DecimalFormat df = new DecimalFormat("0.00"); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -101,14 +103,14 @@ public class ListPage extends AppCompatActivity { pNames.add("Kroger"); pStores.add(""); - pPrices.add(totalPriceByStore.get("Kroger").toString()); + pPrices.add(df.format(totalPriceByStore.get("Kroger"))); pQuantity.add("-1"); pImages.add("-1"); pListItemPair.add(null); pNames.add(item.getDescription()); pStores.add("Kroger"); - pPrices.add(item.getPrice().toString()); + pPrices.add(df.format(item.getPrice())); pQuantity.add(entry.getQuantity().toString()); pImages.add(item.getImageURL()); pListItemPair.add(entry); @@ -117,16 +119,16 @@ public class ListPage extends AppCompatActivity { int index = storeHeaderIndex.get("Kroger"); totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") + (item.getPrice().doubleValue() * entry.getQuantity())); - pPrices.set(index, totalPriceByStore.get("Kroger").toString()); + pPrices.set(index, df.format(totalPriceByStore.get("Kroger"))); double newTotal = Double.parseDouble(pPrices.get(0)) + (item.getPrice().doubleValue() * entry.getQuantity()); - pPrices.set(0, String.valueOf(newTotal)); + pPrices.set(0, df.format(newTotal)); index++; pNames.add(index, item.getDescription()); pStores.add(index, "Kroger"); - pPrices.add(index, item.getPrice().toString()); + pPrices.add(index, df.format(item.getPrice())); pQuantity.add(index, entry.getQuantity().toString()); pImages.add(index, item.getImageURL()); pListItemPair.add(index, entry); @@ -184,9 +186,9 @@ public class ListPage extends AppCompatActivity { int q = Integer.parseInt(pQuantity.get(position)) - 1; pQuantity.set(position, Integer.toString(q)); totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) - Double.parseDouble(pPrices.get(position))); - pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); double newTotal = Double.parseDouble(pPrices.get(0)) - Double.parseDouble(pPrices.get(position)); - pPrices.set(0, String.valueOf(newTotal)); + pPrices.set(0, df.format(newTotal)); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() - 1); pListItemPair.add(position, le); @@ -217,9 +219,9 @@ public class ListPage extends AppCompatActivity { int q = Integer.parseInt(pQuantity.get(position)) + 1; pQuantity.set(position, Integer.toString(q)); totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) + Double.parseDouble(pPrices.get(position))); - pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); double newTotal = Double.parseDouble(pPrices.get(0)) + Double.parseDouble(pPrices.get(position)); - pPrices.set(0, String.valueOf(newTotal)); + pPrices.set(0, df.format(newTotal)); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() + 1); pListItemPair.add(position, le); @@ -269,10 +271,10 @@ public class ListPage extends AppCompatActivity { } else { totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); - pPrices.set(storeHeaderIndex.get(pStores.get(position)), totalPriceByStore.get(pStores.get(position)).toString()); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); double newTotal = Double.parseDouble(pPrices.get(0)) - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position))); - pPrices.set(0, String.valueOf(newTotal)); + pPrices.set(0, df.format(newTotal)); pNames.remove(position); pStores.remove(position); @@ -304,7 +306,7 @@ public class ListPage extends AppCompatActivity { incrQuan.setVisibility(View.INVISIBLE); if(position == 0) { - + removeItem.setText("Clear all"); } else { removeItem.setVisibility(View.INVISIBLE); From 8233a989efed3f89723af65d70313f940bc627bf Mon Sep 17 00:00:00 2001 From: NMerz Date: Sat, 31 Oct 2020 14:54:42 -0400 Subject: [PATCH 28/46] ListDELETE lambda List deletion now working Integration tested independently. Still needs tests and user deletion should now use this. --- Lambdas/Lists/List/src/ListDELETE.java | 12 ++++++ Lambdas/Lists/List/src/ListDeleter.java | 50 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 Lambdas/Lists/List/src/ListDELETE.java create mode 100644 Lambdas/Lists/List/src/ListDeleter.java diff --git a/Lambdas/Lists/List/src/ListDELETE.java b/Lambdas/Lists/List/src/ListDELETE.java new file mode 100644 index 0000000..d202b8d --- /dev/null +++ b/Lambdas/Lists/List/src/ListDELETE.java @@ -0,0 +1,12 @@ +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.util.Map; + +public class ListDELETE implements RequestHandler, Object> { + + public Object handleRequest(Map inputMap, Context unfilled) { + return BasicHandler.handleRequest(inputMap, unfilled, ListDeleter.class); + } + +} diff --git a/Lambdas/Lists/List/src/ListDeleter.java b/Lambdas/Lists/List/src/ListDeleter.java new file mode 100644 index 0000000..0b71411 --- /dev/null +++ b/Lambdas/Lists/List/src/ListDeleter.java @@ -0,0 +1,50 @@ +import java.security.AccessControlException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class ListDeleter implements CallHandler { + private final Connection connection; + private final String cognitoID; + + private final String GET_LISTS = "SELECT * FROM List WHERE (owner = ? AND listID = ?);"; + private final String DELETE_LIST = "DELETE FROM List WHERE listID = ?;"; + private final String DELETE_LIST_ACCESS = "DELETE FROM ListSharee where listID = ?;"; + private final String DELETE_LIST_ENTRIES = "DELETE FROM ListProduct where listID = ?;"; + + public ListDeleter(Connection connection, String cognitoID) { + this.connection = connection; + this.cognitoID = cognitoID; + } + + @Override + public Object conductAction(Map bodyMap, HashMap queryMap, String cognitoID) throws SQLException { + Integer listID = Integer.parseInt(queryMap.get("id")); + PreparedStatement accessCheck = connection.prepareStatement(GET_LISTS); + accessCheck.setString(1, cognitoID); + accessCheck.setInt(2, listID); + System.out.println(accessCheck); + ResultSet userLists = accessCheck.executeQuery(); + + if (!userLists.next()) { + throw new AccessControlException("User does not have access to list"); + } + PreparedStatement cleanAccess = connection.prepareStatement(DELETE_LIST_ACCESS); + cleanAccess.setInt(1, listID); + System.out.println(cleanAccess); + cleanAccess.executeUpdate(); + PreparedStatement deleteEntries = connection.prepareStatement(DELETE_LIST_ENTRIES); + deleteEntries.setInt(1, listID); + System.out.println(deleteEntries); + deleteEntries.executeUpdate(); + PreparedStatement cleanList = connection.prepareStatement(DELETE_LIST); + cleanList.setInt(1, listID); + System.out.println(cleanList); + cleanList.executeUpdate(); + connection.commit(); + return null; + } +} From 739699d7a525ea3128e98b927a86953049e187ff Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sat, 31 Oct 2020 19:54:11 -0400 Subject: [PATCH 29/46] Explicitly declared inner loop variables to final --- Listify/app/src/main/java/com/example/listify/ItemDetails.java | 2 +- .../main/java/com/example/listify/ui/lists/ListsFragment.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ItemDetails.java b/Listify/app/src/main/java/com/example/listify/ItemDetails.java index f2fb43c..46dd926 100644 --- a/Listify/app/src/main/java/com/example/listify/ItemDetails.java +++ b/Listify/app/src/main/java/com/example/listify/ItemDetails.java @@ -106,7 +106,7 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr for (int i = 0; i < listIds.length; i++) { SynchronousReceiver listReceiver = new SynchronousReceiver<>(); requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); - int finalI = i; + final int finalI = i; Thread l = new Thread(new Runnable() { @Override public void run() { diff --git a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java index cec4674..fdcc4d4 100644 --- a/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java +++ b/Listify/app/src/main/java/com/example/listify/ui/lists/ListsFragment.java @@ -48,6 +48,7 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. Requestor requestor; ListView shoppingListsView; ProgressBar loadingLists; + int resultsIndex; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_lists, container, false); @@ -145,7 +146,7 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment. for (int i = 0; i < listIds.length; i++) { SynchronousReceiver listReceiver = new SynchronousReceiver<>(); requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver); - int finalI = i; + final int finalI = i; Thread t = new Thread(new Runnable() { @Override public void run() { From 8364ed049b4eaa27596656beb4e2c5b54104bce8 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sat, 31 Oct 2020 20:10:09 -0400 Subject: [PATCH 30/46] Fixed formatting from merge --- .../main/java/com/example/listify/ListPage.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index 2a551b5..4f2a794 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -168,13 +168,14 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { } } } - runOnUiThread(new Runnable() { - @Override - public void run() { - loadingListItems.setVisibility(View.GONE); - myAdapter.notifyDataSetChanged(); - } - }); + + runOnUiThread(new Runnable() { + @Override + public void run() { + loadingListItems.setVisibility(View.GONE); + myAdapter.notifyDataSetChanged(); + } + }); } } From 4b8561ebd7fd34fac1bdc7773930c57146a9be23 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Sat, 31 Oct 2020 17:25:56 -0700 Subject: [PATCH 31/46] Removed my grand total code (Clayton's job) and reformatted shopping list page --- .../java/com/example/listify/ListPage.java | 102 +++++++----------- .../app/src/main/res/layout/activity_list.xml | 39 +++++-- 2 files changed, 69 insertions(+), 72 deletions(-) diff --git a/Listify/app/src/main/java/com/example/listify/ListPage.java b/Listify/app/src/main/java/com/example/listify/ListPage.java index 6f3044f..c965314 100644 --- a/Listify/app/src/main/java/com/example/listify/ListPage.java +++ b/Listify/app/src/main/java/com/example/listify/ListPage.java @@ -36,6 +36,7 @@ public class ListPage extends AppCompatActivity { Button incrQuan; Button decrQuan; Button removeItem; + Button clearAll; ArrayList pNames = new ArrayList<>(); ArrayList pStores = new ArrayList<>(); @@ -52,15 +53,31 @@ public class ListPage extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { - // Read list ID from caller + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_list); + final int listID = (int) getIntent().getSerializableExtra("listID"); - pNames.add("Total Price"); - pStores.add(""); - pPrices.add("0.00"); - pQuantity.add("-1"); - pImages.add("-1"); - pListItemPair.add(null); + clearAll = (Button) findViewById(R.id.buttonClear); + clearAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + pNames.clear(); + pStores.clear(); + pPrices.clear(); + pQuantity.clear(); + pImages.clear(); + + while(!pListItemPair.isEmpty()) { + try { + requestor.deleteObject(pListItemPair.remove(0)); + } + catch(Exception e) {} + } + + listView.setAdapter(myAdapter); + } + }); Properties configs = new Properties(); try { @@ -98,9 +115,6 @@ public class ListPage extends AppCompatActivity { totalPriceByStore.put("Kroger", item.getPrice().doubleValue() * entry.getQuantity()); storeHeaderIndex.put("Kroger", pNames.size()); - double newTotal = Double.parseDouble(pPrices.get(0)) + (item.getPrice().doubleValue() * entry.getQuantity()); - pPrices.set(0, String.valueOf(newTotal)); - pNames.add("Kroger"); pStores.add(""); pPrices.add(df.format(totalPriceByStore.get("Kroger"))); @@ -121,9 +135,6 @@ public class ListPage extends AppCompatActivity { totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") + (item.getPrice().doubleValue() * entry.getQuantity())); pPrices.set(index, df.format(totalPriceByStore.get("Kroger"))); - double newTotal = Double.parseDouble(pPrices.get(0)) + (item.getPrice().doubleValue() * entry.getQuantity()); - pPrices.set(0, df.format(newTotal)); - index++; pNames.add(index, item.getDescription()); @@ -143,12 +154,8 @@ public class ListPage extends AppCompatActivity { } } - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_list); - listView = findViewById(R.id.listView); myAdapter = new MyAdapter(this, pNames, pStores, pPrices, pQuantity, pImages); - listView.setAdapter(myAdapter); } @@ -187,8 +194,6 @@ public class ListPage extends AppCompatActivity { pQuantity.set(position, Integer.toString(q)); totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) - Double.parseDouble(pPrices.get(position))); pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); - double newTotal = Double.parseDouble(pPrices.get(0)) - Double.parseDouble(pPrices.get(position)); - pPrices.set(0, df.format(newTotal)); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() - 1); pListItemPair.add(position, le); @@ -220,8 +225,6 @@ public class ListPage extends AppCompatActivity { pQuantity.set(position, Integer.toString(q)); totalPriceByStore.put(pStores.get(position), totalPriceByStore.get(pStores.get(position)) + Double.parseDouble(pPrices.get(position))); pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); - double newTotal = Double.parseDouble(pPrices.get(0)) + Double.parseDouble(pPrices.get(position)); - pPrices.set(0, df.format(newTotal)); ListEntry le = pListItemPair.remove(position); le.setQuantity(le.getQuantity() + 1); pListItemPair.add(position, le); @@ -249,41 +252,16 @@ public class ListPage extends AppCompatActivity { removeItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if(position == 0) { - pNames.clear(); - pStores.clear(); - pPrices.clear(); - pQuantity.clear(); - pImages.clear(); + totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); + pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); - pNames.add("Total Price"); - pStores.add(""); - pPrices.add("0.00"); - pQuantity.add("-1"); - pImages.add("-1"); + pNames.remove(position); + pStores.remove(position); + pPrices.remove(position); + pQuantity.remove(position); + pImages.remove(position); - while(pListItemPair.size() > 1) { - try { - requestor.deleteObject(pListItemPair.remove(1)); - } - catch(Exception e) {} - } - } - else { - totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); - pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); - - double newTotal = Double.parseDouble(pPrices.get(0)) - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position))); - pPrices.set(0, df.format(newTotal)); - - pNames.remove(position); - pStores.remove(position); - pPrices.remove(position); - pQuantity.remove(position); - pImages.remove(position); - - requestor.deleteObject(pListItemPair.remove(position)); - } + requestor.deleteObject(pListItemPair.remove(position)); listView.setAdapter(myAdapter); } @@ -301,23 +279,17 @@ public class ListPage extends AppCompatActivity { price.setText("$" + pPrices.get(position)); if(pQuantity.get(position).equals("-1")) { - quantity.setVisibility(View.INVISIBLE); - decrQuan.setVisibility(View.INVISIBLE); - incrQuan.setVisibility(View.INVISIBLE); - - if(position == 0) { - removeItem.setText("Clear all"); - } - else { - removeItem.setVisibility(View.INVISIBLE); - } + quantity.setVisibility(View.GONE); + decrQuan.setVisibility(View.GONE); + incrQuan.setVisibility(View.GONE); + removeItem.setVisibility(View.GONE); } else { quantity.setText(pQuantity.get(position)); } if(pImages.get(position).equals("-1")) { - image.setVisibility(View.INVISIBLE); + image.setVisibility(View.GONE); } else { Glide.with(getContext()).load(pImages.get(position)).into(image); diff --git a/Listify/app/src/main/res/layout/activity_list.xml b/Listify/app/src/main/res/layout/activity_list.xml index 65c12f4..2dc9220 100644 --- a/Listify/app/src/main/res/layout/activity_list.xml +++ b/Listify/app/src/main/res/layout/activity_list.xml @@ -1,14 +1,39 @@ - - +