Для решения поставленной задачи разработки приложения был использован пакет ADT (Android Developer Tools) Bundle. Это мощный и удобный инструмент, уже проверенный годами. Представляя собой, по сути, модифицированный Eclipse, этот пакет удобен тем, кто знаком с этой средой разработки. Плагин ADT также включает визуальный инструмент, применяемый для создания графического интерфейса пользователя.
Пакет включает в себя:
o Eclipse — интегрированная среда разработки для Android. Поскольку среда Eclipse также написана на Java, ее можно установить на PC, Mac или компьютер с системой Linux.
o Android Developer Tools — плагин для Eclipse.
o Android SDK
o Инструменты и платформенные средства Android SDK — средства отладки и тестирования приложений.
o Образ системы для эмулятора Android — позволяет создавать и тестировать приложения на различных виртуальных устройствах.
Сравнение с аналогами
Android является на сегодняшний день самой популярной операционной системой, под которую написано огромное число приложений. На начало 2015 года в Google Play было 1,43 млн программ. Разумеется среди них множество обучающих приложений схожего плана. Однако особенностями приложение FlashCards является наличие татарских слов и возможность записывать свои варианты произношения слова, что выводит обучение на новый уровень.
Заключение
В результате выполнения задания удалось выполнить поставленные задачи. Приложение обладает всем требуемым функционалом.
Представляя собой готовое рабочее приложение, FlashCards уже сейчас готово к выходу на рынок мобильных приложений, нуждаясь лишь в дизайне и расширении набора слов, что не является проблемой с учетом гибкой структуры программы.
В ходе выполнения проекта были изучена технология Android. Были освоены основные принципы и характерные особенности разработки приложения под данную ОС. В их числе работа с активностями, модель MVC, использование UI-фрагментов (фрагментов пользовательского интерфейса), работа с ресурсами и др.
Список используемых источников
1. Б. Харди, Б. Филлипс. Программирование под Android. Для профессионалов: Пер. с англ.— СПб.: Питер, 2014.
2. Медникс З., Дорнин Л., Мик Б., Накамура М. Программирование под Android: Пер с англ. — СПб.: Питер, 2013.
Листинг
package com.akishinde.flashcards;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONException;
import org.json.JSONObject;
public class Card {
private static final String JSON_ID = "id";
private static final String JSON_TOPIC = "topic";
private static final String JSON_USERSOUND_LANGUAGE = "usersound_language";
private static final String JSON_USERSOUND_FILENAME = "usersound_filename";
private static final String JSON_USERSOUND_SELECTED = "usersound_selected";
private int mId;
private int mTitle;
private int mImage;
private int mSound;
private HashMap<Settings.LANGUAGE, String> mUserSoundsFileNames;
private HashMap<Settings.LANGUAGE, Boolean> mUserSoundsSelected;
public Card(int id, int title, int img, int sound) {
mId = id;
mTitle = title;
mImage = img;
mSound = sound;
mUserSoundsFileNames = new HashMap<Settings.LANGUAGE, String>();
mUserSoundsSelected = new HashMap<Settings.LANGUAGE, Boolean>();
for (Settings.LANGUAGE lang: Settings.LANGUAGE.values()) {
mUserSoundsFileNames.put(lang, null);
mUserSoundsSelected.put(lang, false);
}
}
public ArrayList<JSONObject> userSoundInfoToJSON(CardLab.TOPIC topic)
throws JSONException {
ArrayList<JSONObject> jsonArray = new ArrayList<JSONObject>();
for (Settings.LANGUAGE lang: Settings.LANGUAGE.values()) {
String userSoundFileName = mUserSoundsFileNames.get(lang);
if (userSoundFileName!= null) {
JSONObject json = new JSONObject();
json.put(JSON_ID, mId);
json.put(JSON_TOPIC, topic);
json.put(JSON_USERSOUND_LANGUAGE, lang);
json.put(JSON_USERSOUND_FILENAME, userSoundFileName);
json.put(JSON_USERSOUND_SELECTED, mUserSoundsSelected.get(lang));
jsonArray.add(json);
}
}
return jsonArray;
}
public static CardLab.TOPIC topicFromJSON(JSONObject json) throws JSONException{
return (CardLab.TOPIC)json.get(JSON_TOPIC);
}
public static int idFromJSON(JSONObject json) throws JSONException{
return (Integer)json.get(JSON_ID);
}
public void userSoundInfoFromJSON(JSONObject json) throws JSONException{
Settings.LANGUAGE lang = (Settings.LANGUAGE)json.get(JSON_USERSOUND_LANGUAGE);
String filename = (String)json.get(JSON_USERSOUND_FILENAME);
boolean selected = (Boolean)json.get(JSON_USERSOUND_SELECTED);
setUserSoundFileName(lang, filename);
setUserSoundSelected(lang, selected);
}
public int getTitle() {
return mTitle;
}
public void setTitle(int title) {
mTitle = title;
}
public int getImage() {
return mImage;
}
public void setImage(int image) {
mImage = image;
}
public int getSound() {
return mSound;
}
public void setSound(int sound) {
mSound = sound;
}
public int getId() {
return mId;
}
public String getUserSoundFileName(Settings.LANGUAGE lang) {
return mUserSoundsFileNames.get(lang);
}
public void setUserSoundFileName(Settings.LANGUAGE lang,
String soundFileName) {
mUserSoundsFileNames.put(lang, soundFileName);
}
public boolean isUserSoundSelected(Settings.LANGUAGE lang) {
return mUserSoundsSelected.get(lang);
}
public void setUserSoundSelected(Settings.LANGUAGE lang, boolean selected) {
mUserSoundsSelected.put(lang, selected);
}
public void switchUserSoundSelected(Settings.LANGUAGE lang) {
mUserSoundsSelected.put(lang,!mUserSoundsSelected.get(lang));
}
}
package com.akishinde.flashcards;
import java.util.UUID;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.NavUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class CardFragment extends Fragment {
public static final String EXTRA_CARD_ID = "com.akishinde.flashcards.card_id";
public static final String EXTRA_TOPIC_NAME = "com.akishinde.flashcards.topic_name";
private static final String DIALOG_USER_SOUND = "user_sound";
private static final int REQUEST_SOUND_FILENAME = 0;
private Card mCard;
private CardLab.TOPIC mTopic;
private TextView mTitleView;
private ImageButton mImageButton;
private ImageButton mUserSoundButton;
private Settings.LANGUAGE mLanguage;
public static CardFragment newInstance(CardLab.TOPIC topicName, int cardId) {
Bundle args = new Bundle();
args.putSerializable(EXTRA_CARD_ID, cardId);
args.putSerializable(EXTRA_TOPIC_NAME, topicName);
CardFragment fragment = new CardFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int cardId = (Integer) getArguments().getSerializable(EXTRA_CARD_ID);
mTopic = (CardLab.TOPIC) getArguments().getSerializable(
EXTRA_TOPIC_NAME);
mCard = CardLab.get(getActivity()).getCard(mTopic, cardId);
mLanguage = Settings.get(getActivity()).getLanguage();
setHasOptionsMenu(true);
}
@TargetApi(11)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_card, parent, false);
RelativeLayout background = (RelativeLayout) v
.findViewById(R.id.card_background);
background.setBackgroundColor(getResources().getColor(
CardLab.get(getActivity()).getBackgroundColor(mTopic)));
mTitleView = (TextView) v.findViewById(R.id.card_title_textview);
mTitleView.setText(mCard.getTitle());
updateTitleVisibility();
mImageButton = (ImageButton) v.findViewById(R.id.card_image_button);
mImageButton.setImageResource(mCard.getImage());
mImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
SingletonAudioPlayer.get(getActivity()).playCard(mCard);
}
});
mUserSoundButton = (ImageButton) v
.findViewById(R.id.card_usersound_button);
updateUserSoundButton();
mUserSoundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
mCard.switchUserSoundSelected(mLanguage);
CardLab.get(getActivity()).saveUserSoundInfo();
updateUserSoundButton();
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (NavUtils.getParentActivityName(getActivity())!= null)
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}
return v;
}
public void updateTitleVisibility() {
if (Settings.get(getActivity()).isCardTitleVisible())
mTitleView.setVisibility(View.VISIBLE);
else
mTitleView.setVisibility(View.INVISIBLE);
}
private void updateUserSoundButton() {
if (mCard.getUserSoundFileName(mLanguage) == null)
mUserSoundButton.setVisibility(View.INVISIBLE);
else {
mUserSoundButton.setVisibility(View.VISIBLE);
if (mCard.isUserSoundSelected(mLanguage))
mUserSoundButton.setImageResource(R.drawable.icon_usersound_on);
else
mUserSoundButton
.setImageResource(R.drawable.icon_usersound_off);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_card, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (NavUtils.getParentActivityName(getActivity())!= null)
NavUtils.navigateUpFromSameTask(getActivity());
return true;
case R.id.menu_item_userSound:
UserSoundFragment userSoundDialog = UserSoundFragment
.newInstance(mCard.getUserSoundFileName(mLanguage));
userSoundDialog.setTargetFragment(CardFragment.this,
REQUEST_SOUND_FILENAME);
userSoundDialog.show(getActivity().getSupportFragmentManager(),
DIALOG_USER_SOUND);
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode!= Activity.RESULT_OK)
return;
if (requestCode == REQUEST_SOUND_FILENAME) {
String soundFileName = data
.getStringExtra(UserSoundFragment.EXTRA_SOUND_FILENAME);
if (soundFileName!= mCard.getUserSoundFileName(mLanguage)) {
mCard.setUserSoundFileName(mLanguage, soundFileName);
mCard.setUserSoundSelected(mLanguage, (soundFileName!= null));
CardLab.get(getActivity()).saveUserSoundInfo();
}
updateUserSoundButton();
}
}
}
package com.akishinde.flashcards;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONObject;
import android.content.Context;
import android.util.Log;
public class CardLab {
private static CardLab sCardLab;
private Context mAppContext;
private static final String TAG = "CardLab";
private FlashCardsJSONSerializer mSerializer;
private static final String FILENAME = "user_sound_info.json";
private ArrayList<Card> mAnimalsCards;
private ArrayList<Card> mFoodCards;
private ArrayList<Card> mTransportCards;
enum TOPIC {
Animals, Food, Transport
}
private HashMap<TOPIC, Integer> backgroundColorMap;
private CardLab(Context appContext) {
mAppContext = appContext;
backgroundColorMap = new HashMap<TOPIC, Integer>();
mSerializer = new FlashCardsJSONSerializer(mAppContext);
int id = 0;
mFoodCards = new ArrayList<Card>();
mFoodCards.add(new Card(id++, R.string.card_food_apple,
R.drawable.card_food_apple, R.raw.sound_card_food_apple));
mFoodCards.add(new Card(id++, R.string.card_food_tomato,
R.drawable.card_food_tomato, R.raw.sound_card_food_tomato));
mFoodCards.add(new Card(id++, R.string.card_food_cucumber,
R.drawable.card_food_cucumber, R.raw.sound_card_food_cucumber));
mFoodCards.add(new Card(id++, R.string.card_food_carrot,
R.drawable.card_food_carrot, R.raw.sound_card_food_carrot));
mFoodCards.add(new Card(id++, R.string.card_food_cheese,
R.drawable.card_food_cheese, R.raw.sound_card_food_cheese));
backgroundColorMap.put(TOPIC.Food, R.color.color_background_topic_food);
mAnimalsCards = new ArrayList<Card>();
mAnimalsCards.add(new Card(id++, R.string.card_animal_dog,
R.drawable.card_animal_dog, R.raw.sound_card_animal_dog));
mAnimalsCards.add(new Card(id++, R.string.card_animal_tiger,
R.drawable.card_animal_tiger, R.raw.sound_card_animal_tiger));
mAnimalsCards.add(new Card(id++, R.string.card_animal_horse,
R.drawable.card_animal_horse, R.raw.sound_card_animal_horse));
backgroundColorMap.put(TOPIC.Animals,
R.color.color_background_topic_animals);
mTransportCards = new ArrayList<Card>();
mTransportCards.add(new Card(id++, R.string.card_transport_plane,
R.drawable.card_transport_plane,
R.raw.sound_card_transport_plane));
mTransportCards.add(new Card(id++, R.string.card_transport_helicopter,
R.drawable.card_transport_helicopter,
R.raw.sound_card_transport_helicopter));
mTransportCards.add(new Card(id++, R.string.card_transport_bus,
R.drawable.card_transport_bus, R.raw.sound_card_transport_bus));
backgroundColorMap.put(TOPIC.Transport,
R.color.color_background_topic_transport);
loadUserSoundInfo();
}
public static CardLab get(Context c) {
if (sCardLab == null) {
sCardLab = new CardLab(c.getApplicationContext());
}
return sCardLab;
}
public boolean saveUserSoundInfo() {
try {
HashMap<TOPIC, ArrayList<Card>> cardTopics = new HashMap<TOPIC, ArrayList<Card>>();
cardTopics.put(TOPIC.Animals, mAnimalsCards);
cardTopics.put(TOPIC.Food, mFoodCards);
cardTopics.put(TOPIC.Transport, mTransportCards);
mSerializer.saveUserSoundInfo(FILENAME, cardTopics);
Log.d(TAG, "userSoundInfo saved to file");
return true;
} catch (Exception e) {
Log.e(TAG, "Error saving userSoundInfo: ", e);
return false;
}
}
private boolean loadUserSoundInfo() {
ArrayList<JSONObject> jsonArray;
try {
jsonArray = mSerializer.loadUserSoundInfo(FILENAME);
for (JSONObject json: jsonArray) {
Card card;
card = getCard(Card.topicFromJSON(json), Card.idFromJSON(json));
if (card!= null)
card.userSoundInfoFromJSON(json);
}
Log.d(TAG, "userSoundInfo loaded");
return true;
} catch (Exception e) {
Log.e(TAG, "Error loading userSoundInfo " + e);
return false;
}
}
public ArrayList<Card> getCards(TOPIC topic) {
switch (topic) {
case Animals:
return mAnimalsCards;
case Food:
return mFoodCards;
case Transport:
return mTransportCards;
default:
return null;
}
}
public Card getCard(TOPIC topic, int id) {
switch (topic) {
case Animals:
return getCard(mAnimalsCards, id);
case Food:
return getCard(mFoodCards, id);
case Transport:
return getCard(mTransportCards, id);
default:
return null;
}
}
private Card getCard(ArrayList<Card> cards, int id) {
for (Card c: cards) {
if (c.getId() == id)
return c;
}
return null;
}
public int getBackgroundColor(TOPIC topic) {
return backgroundColorMap.get(topic);
}
}
package com.akishinde.flashcards;
import java.util.ArrayList;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
public class CardPagerActivity extends LocaleSettingFragmentActivity {
private static final String DIALOG_SLIDESHOW_SETTINGS = "slideshow_settings";
private CardLab.TOPIC mTopic;
private ViewPager mViewPager;
private boolean mPageScrolledButNotSelected;
private ArrayList<Card> mCards;
private boolean mFirstCardPlayed;
private static final String KEY_FIRST_CARD_PLAYED = "first_card_played";
private boolean mSlideShowStarted;
private static final String KEY_SLIDESHOW_STARTED = "slideshow_started";
private final Handler mHandler = new Handler();
private Runnable mViewPagerAutoScroll;
private MenuItem mSlideShowMenuItem;
private MenuItem mSlideShowSettingsMenuItem;
private int mSlideShowInterval;
private PagerAdapterWithFragmentRegistration mPagerAdapter;
private class PagerAdapterWithFragmentRegistration extends
FragmentStatePagerAdapter {
private SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public PagerAdapterWithFragmentRegistration(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return mCards.size();
}
@Override
public Fragment getItem(int pos) {
Card card = mCards.get(pos);
return CardFragment.newInstance(mTopic, card.getId());
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container,
position);
registeredFragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public void updateRegisteredFragments() {
int size = registeredFragments.size();
for (int i = 0; i < size; i++) {
((CardFragment) registeredFragments.valueAt(i))
.updateTitleVisibility();
}
}
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_OK);
if (savedInstanceState!= null) {
mSlideShowStarted = savedInstanceState
.getBoolean(KEY_SLIDESHOW_STARTED);
mFirstCardPlayed = savedInstanceState
.getBoolean(KEY_FIRST_CARD_PLAYED);
} else {
mSlideShowStarted = false;
mFirstCardPlayed = false;
}
mTopic = (CardLab.TOPIC) getIntent().getSerializableExtra(
CardFragment.EXTRA_TOPIC_NAME);
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.viewPager);
setContentView(mViewPager);
mCards = CardLab.get(this).getCards(mTopic);
if (!mFirstCardPlayed) {
playCurrentCard(0);
mFirstCardPlayed = true;
}
FragmentManager fm = getSupportFragmentManager();
mPagerAdapter = new PagerAdapterWithFragmentRegistration(fm);
mViewPager.setAdapter(mPagerAdapter);
mViewPager
.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int pos) {
if (mPageScrolledButNotSelected) {
Card card = mCards.get(pos);
SingletonAudioPlayer.get(CardPagerActivity.this)
.playCard(card);
mPageScrolledButNotSelected = false;
}
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
mPageScrolledButNotSelected = true;
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
});
}
private void playCurrentCard(int currentIndex) {
if (mCards.size()!= 0) {
Card card = mCards.get(currentIndex);
if (card!= null)
SingletonAudioPlayer.get(CardPagerActivity.this).playCard(
card);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_cardpager, menu);
mSlideShowMenuItem = menu.findItem(R.id.menu_item_slideshow);
mSlideShowSettingsMenuItem = menu
.findItem(R.id.menu_item_slideshow_settings);
updateMenuSlideShowItems(menu.findItem(R.id.menu_item_slideshow),
mSlideShowStarted);
updateMenuSoundItem(menu.findItem(R.id.menu_item_sound));
updateMenuTitleVisibilityItem(menu
.findItem(R.id.menu_item_titleVisibility));
return true;
}
private void updateMenuSoundItem(MenuItem item) {
if (Settings.get(this).isSoundOn()) {
item.setTitle(R.string.sound_on);
item.setIcon(R.drawable.icon_menu_sound_on);
} else {
item.setTitle(R.string.sound_off);
item.setIcon(R.drawable.icon_menu_sound_off);
}
}
private void updateMenuTitleVisibilityItem(MenuItem item) {
if (Settings.get(this).isCardTitleVisible()) {
item.setTitle(R.string.title_visible);
} else {
item.setTitle(R.string.title_invisible);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_sound:
Settings.get(this).switchSound();
SingletonAudioPlayer.get(this).stop();
updateMenuSoundItem(item);
return true;
case R.id.menu_item_titleVisibility:
Settings.get(this).switchCardTitleVisibility();
updateMenuTitleVisibilityItem(item);
mPagerAdapter.updateRegisteredFragments();
return true;
case R.id.menu_item_slideshow:
if (!mSlideShowStarted) {
startSlideShow();
} else
stopSlideShow();
return true;
case R.id.menu_item_slideshow_settings:
SlideShowSettingsFragment dialog = new SlideShowSettingsFragment();
dialog.show(getSupportFragmentManager(), DIALOG_SLIDESHOW_SETTINGS);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void startSlideShow() {
mSlideShowInterval = Settings.get(this).getSlideShowInterval();
mSlideShowStarted = true;
updateMenuSlideShowItems(mSlideShowMenuItem, mSlideShowStarted);
if (Settings.get(this).isSlideShowFromTheBeginning())
mViewPager.setCurrentItem(0);
playCurrentCard(mViewPager.getCurrentItem());
mViewPagerAutoScroll = createViewPagerAutoScroll();
mHandler.postDelayed(mViewPagerAutoScroll, mSlideShowInterval);
}
private void stopSlideShow() {
mSlideShowStarted = false;
updateMenuSlideShowItems(mSlideShowMenuItem, mSlideShowStarted);
mHandler.removeCallbacks(mViewPagerAutoScroll);
}
private void pauseSlideShow() {
if (mHandler!= null)
mHandler.removeCallbacks(mViewPagerAutoScroll);
}
private void updateMenuSlideShowItems(MenuItem item, boolean started) {
if (started) {
item.setTitle(R.string.slideshow_stop);
item.setIcon(R.drawable.icon_menu_slideshow_stop);
} else {
item.setTitle(R.string.slideshow_start);
item.setIcon(R.drawable.icon_menu_slideshow_start);
}
if (mSlideShowSettingsMenuItem!= null)
mSlideShowSettingsMenuItem.setEnabled(!started);
}
private Runnable createViewPagerAutoScroll() {
return new Runnable() {
@Override
public void run() {
int currIndex = mViewPager.getCurrentItem();
PagerAdapter adapter = mViewPager.getAdapter();
if (currIndex < adapter.getCount() - 1) {
mViewPager.setCurrentItem(currIndex + 1, true);
mHandler.postDelayed(mViewPagerAutoScroll,
mSlideShowInterval);
currIndex++;
} else
stopSlideShow();
}
};
}
@Override
public void onPause() {
super.onPause();
SingletonAudioPlayer.get(CardPagerActivity.this).stop();
pauseSlideShow();
}
@Override
public void onResume() {
super.onResume();
if (mSlideShowStarted)
startSlideShow();
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean(KEY_SLIDESHOW_STARTED, mSlideShowStarted);
savedInstanceState.putBoolean(KEY_FIRST_CARD_PLAYED, mFirstCardPlayed);
}
}
package com.akishinde.flashcards;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import android.content.Context;
public class FlashCardsJSONSerializer {
private Context mContext;
public FlashCardsJSONSerializer(Context context) {
mContext = context;
}
public void saveSettings(String fileName) throws JSONException, IOException {
Writer writer = null;
try {
OutputStream out = mContext.openFileOutput(fileName,
Context.MODE_PRIVATE);
writer = new OutputStreamWriter(out);
writer.write(Settings.get(mContext).settingsToJSON().toString());
} finally {
if (writer!= null)
writer.close();
}
}
public JSONObject loadSettings(String fileName) throws IOException,
JSONException {
BufferedReader reader = null;
try {
InputStream in = mContext.openFileInput(fileName);
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder jsonString = new StringBuilder();
String line = null;
while ((line = reader.readLine())!= null) {
jsonString.append(line);
}
JSONArray array = (JSONArray) new JSONTokener(jsonString.toString())
.nextValue();
return array.getJSONObject(0);
} catch (FileNotFoundException e) {
} finally {
if (reader!= null)
reader.close();
}
return null;
}
public void saveUserSoundInfo(String fileName,
HashMap<CardLab.TOPIC, ArrayList<Card>> cardTopics)
throws JSONException, IOException {
JSONArray array = new JSONArray();
for (CardLab.TOPIC topic: cardTopics.keySet()) {
for (Card card: cardTopics.get(topic))
for (JSONObject jsonObject: card.userSoundInfoToJSON(topic))
array.put(jsonObject);
}
Writer writer = null;
try {
OutputStream out = mContext.openFileOutput(fileName,
Context.MODE_PRIVATE);
writer = new OutputStreamWriter(out);
writer.write(array.toString());
} finally {
if (writer!= null)
writer.close();
}
}
public ArrayList<JSONObject> loadUserSoundInfo(String fileName) throws IOException, JSONException {
ArrayList<JSONObject> jsonArray = new ArrayList<JSONObject>();
BufferedReader reader = null;
try {
InputStream in = mContext.openFileInput(fileName);
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder jsonString = new StringBuilder();
String line = null;
while ((line = reader.readLine())!= null) {
jsonString.append(line);
}
JSONArray array = (JSONArray) new JSONTokener(jsonString.toString()).nextValue();
} catch (FileNotFoundException e) {
} finally {
if (reader!= null)
reader.close();
}
return jsonArray;
}
}
package com.akishinde.flashcards;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.RadioGroup;
public class LanguageChoiceFragment extends DialogFragment {
private RadioGroup mLanguagesRadio;
private void sendResult(int resultCode) {
if (getTargetFragment() == null)
return;
getTargetFragment().onActivityResult(getTargetRequestCode(),
resultCode, null);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View v = getActivity().getLayoutInflater().inflate(
R.layout.dialog_language, null);
mLanguagesRadio = (RadioGroup) v
.findViewById(R.id.languages_radiogroup);
Settings.LANGUAGE lang = Settings.get(getActivity()).getLanguage();
switch (lang) {
case Russian:
mLanguagesRadio.check(R.id.russian_language_radiobutton);
break;
case Tatar:
mLanguagesRadio.check(R.id.tatar_language_radiobutton);
break;
default:
}
return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.language)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
switch (mLanguagesRadio
.getCheckedRadioButtonId()) {
case R.id.russian_language_radiobutton:
Settings.get(getActivity()).setLanguage(
Settings.LANGUAGE.Russian);
break;
case R.id.tatar_language_radiobutton:
Settings.get(getActivity()).setLanguage(
Settings.LANGUAGE.Tatar);
break;
default:
}
sendResult(Activity.RESULT_OK);
}
}).create();
}
}
package com.akishinde.flashcards;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public abstract class LocaleSettingFragmentActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Settings.get(this).set();
}
}
package com.akishinde.flashcards;
import java.io.File;
import java.util.Locale;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
public class Settings {
private static Settings sSettings;
private Context mAppContext;
private static final String TAG = "Settings";
private static final String APPLICATION_FOLDER_NAME = "FlashCards";
private FlashCardsJSONSerializer mSerializer;
private static final String SETTINGS_FILENAME = "settings.json";
private static final String JSON_LANGUAGE = "language";
private static final String JSON_SOUND_ON = "sound_on";
private static final String JSON_CARDTITLE_VISIBLE = "cardtitle_visible";
private static final String JSON_SLIDESHOW_INTERVAL = "slideshow_interval";
private static final String JSON_SLIDESHOW_FROMTHEBEGINNING = "slideshow_fromthebeginning";
public enum LANGUAGE {
Russian, Tatar
};
private LANGUAGE mLanguage;
private LANGUAGE mDefaultLanguage = LANGUAGE.Russian;
private String mDefaultQualifier = "ru";
private boolean mSoundOn;
private boolean mDefaultSoundOn = true;
private boolean mCardTitleVisible;
private boolean mDefaultCardTitleVisible = true;
private int mSlideShowInterval; // in millisec
private int mMaxSlideShowInterval = 5000;
private int mMinSlideShowInterval = 500;
private int mDefaultSlideShowInterval = 1000;
private boolean mSlideShowFromTheBeginning;
private boolean mDefaultSlideShowFromTheBeginning = false;
public static Settings get(Context c) {
if (sSettings == null) {
sSettings = new Settings(c.getApplicationContext());
}
return sSettings;
}
private Settings(Context c) {
mAppContext = c;
mSerializer = new FlashCardsJSONSerializer(mAppContext);
loadSettings();
}
public void set() {
setLocale();
}
private boolean saveSettings() {
try {
mSerializer.saveSettings(SETTINGS_FILENAME);
return true;
} catch (Exception e) {
Log.e(TAG, "Error saving settings: ", e);
return false;
}
}
private void loadSettings() {
setDefault();
try {
JSONObject json = mSerializer.loadSettings(SETTINGS_FILENAME);
mLanguage = LANGUAGE.valueOf(json.getString(JSON_LANGUAGE));
mCardTitleVisible = json.getBoolean(JSON_CARDTITLE_VISIBLE);
mSlideShowInterval = json.getInt(JSON_SLIDESHOW_INTERVAL);
mSoundOn = json.getBoolean(JSON_SOUND_ON);
mSlideShowFromTheBeginning = json
.getBoolean(JSON_SLIDESHOW_FROMTHEBEGINNING);
} catch (Exception e) {
Log.e(TAG, "Error loading settings: ", e);
}
}
private void setDefault() {
mLanguage = mDefaultLanguage;
mSlideShowInterval = mDefaultSlideShowInterval;
mSoundOn = mDefaultSoundOn;
mSlideShowFromTheBeginning = mDefaultSlideShowFromTheBeginning;
mCardTitleVisible = mDefaultCardTitleVisible;
}
public JSONObject settingsToJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put(JSON_LANGUAGE, mLanguage);
json.put(JSON_SOUND_ON, mSoundOn);
json.put(JSON_CARDTITLE_VISIBLE, mCardTitleVisible);
json.put(JSON_SLIDESHOW_INTERVAL, mSlideShowInterval);
json.put(JSON_SLIDESHOW_FROMTHEBEGINNING, mSlideShowFromTheBeginning);
return json;
}
public LANGUAGE getLanguage() {
return mLanguage;
}
public void setLanguage(LANGUAGE lang) {
mLanguage = lang;
setLocale();
saveSettings();
}
private void setLocale() {
String qualifier;
switch (mLanguage) {
case Russian:
qualifier = "ru";
break;
case Tatar:
qualifier = "tt";
break;
default:
qualifier = mDefaultQualifier;
break;
}
Locale locale = new Locale(qualifier);
Locale.setDefault(locale);
android.content.res.Configuration config = new android.content.res.Configuration();
config.locale = locale;
mAppContext.getResources().updateConfiguration(config,
mAppContext.getResources().getDisplayMetrics());
}
public boolean isSoundOn() {
return mSoundOn;
}
public void setSoundOn(boolean soundOn) {
mSoundOn = soundOn;
}
public void switchSound() {
mSoundOn =!mSoundOn;
saveSettings();
}
public boolean isCardTitleVisible() {
return mCardTitleVisible;
}
public void setCardTitleVisible(boolean cardTitleVisible) {
mCardTitleVisible = cardTitleVisible;
}
public void switchCardTitleVisibility() {
mCardTitleVisible =!mCardTitleVisible;
saveSettings();
}
public int getSlideShowInterval() {
return mSlideShowInterval;
}
public void setSlideShowInterval(int slideShowInterval) {
if (slideShowInterval <= 0)
mSlideShowInterval = mMinSlideShowInterval;
else
mSlideShowInterval = slideShowInterval;
saveSettings();
}
public void setSlideShowIntervalBySeconds(int slideShowIntervalInSeconds) {
setSlideShowInterval(slideShowIntervalInSeconds * 1000);
}
public int getMaxSlideShowInterval() {
return mMaxSlideShowInterval;
}
public boolean isSlideShowFromTheBeginning() {
return mSlideShowFromTheBeginning;
}
public void setSlideShowFromTheBeginning(boolean slideShowFromTheBeginning) {
mSlideShowFromTheBeginning = slideShowFromTheBeginning;
saveSettings();
}
public String getApplicationExternalStorage() {
File appFolder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/" + APPLICATION_FOLDER_NAME);
appFolder.mkdir();
return appFolder.getAbsolutePath();
}
}
package com.akishinde.flashcards;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
public abstract class SingleFragmentActivity extends LocaleSettingFragmentActivity {
protected abstract Fragment createFragment();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction().add(R.id.fragmentContainer, fragment)
.commit();
}
}
}
package com.akishinde.flashcards;
import android.content.Context;
import android.media.MediaPlayer;
import android.util.Log;
public class SingletonAudioPlayer {
private static final String TAG = "AudioPlayer";
private MediaPlayer mPlayer;
private Context mAppContext;
private static SingletonAudioPlayer sSingletonAudioPlayer;;
private SingletonAudioPlayer(Context appContext) {
mAppContext = appContext;
}
public static SingletonAudioPlayer get(Context c) {
if (sSingletonAudioPlayer == null) {
sSingletonAudioPlayer = new SingletonAudioPlayer(
c.getApplicationContext());
}
return sSingletonAudioPlayer;
}
public void stop() {
if (mPlayer!= null) {
mPlayer.release();
mPlayer = null;
}
}
public void play(int sound) {
if (Settings.get(mAppContext).isSoundOn()) {
stop();
mPlayer = MediaPlayer.create(mAppContext, sound);
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
stop();
}
});
mPlayer.start();
}
}
public void playFromExternalStorage(String soundFileName) {
if (Settings.get(mAppContext).isSoundOn()) {
stop();
boolean preparationSuccess = true;
mPlayer = new MediaPlayer();
try {
mPlayer.setDataSource(Settings.get(mAppContext)
.getApplicationExternalStorage() + "/" + soundFileName);
mPlayer.prepare();
} catch (Exception e) {
Log.e(TAG, "Error playing file " + e);
preparationSuccess = false;
}
if (preparationSuccess) {
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
stop();
}
});
mPlayer.start();
} else {
Log.d(TAG, "mPlayer released()");
mPlayer.release();
}
}
}
public interface OnPlayingCompletionListener {
public void onPlayingCompletion();
}
public void playFromExternalStorage(String soundFileName,
OnPlayingCompletionListener onPlayingCompletionListener) {
if (Settings.get(mAppContext).isSoundOn()) {
stop();
boolean preparationSuccess = true;
mPlayer = new MediaPlayer();
try {
mPlayer.setDataSource(Settings.get(mAppContext)
.getApplicationExternalStorage() + "/" + soundFileName);
mPlayer.prepare();
} catch (Exception e) {
Log.e(TAG, "Error playing file " + e);
preparationSuccess = false;
}
if (preparationSuccess) {
mPlayer.setOnCompletionListener(new OnMediaPlayerCompletionListener(
onPlayingCompletionListener));
mPlayer.start();
} else {
mPlayer.release();
}
}
}
private class OnMediaPlayerCompletionListener implements
MediaPlayer.OnCompletionListener {
SingletonAudioPlayer.OnPlayingCompletionListener mOnPlayingCompletionListener;
OnMediaPlayerCompletionListener(
SingletonAudioPlayer.OnPlayingCompletionListener onPlayingCompletionListener) {
mOnPlayingCompletionListener = onPlayingCompletionListener;
}
@Override
public void onCompletion(MediaPlayer arg0) {
stop();
if (mOnPlayingCompletionListener!= null) {
mOnPlayingCompletionListener.onPlayingCompletion();
}
}
}
public void playCard(Card card) {
Settings.LANGUAGE language = Settings.get(mAppContext).getLanguage();
if (card.isUserSoundSelected(language))
playFromExternalStorage(card.getUserSoundFileName(language));
else
play(card.getSound());
}
}
package com.akishinde.flashcards;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.CheckBox;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
public class SlideShowSettingsFragment extends DialogFragment {
private SeekBar mIntervalSeekBar;
private CheckBox mFromTheBeginningCheckBox;
private TextView mIntervalField;
private double mStep = 0.5;
private final int MSEC_IN_SEC = 1000;
private void sendResult(int resultCode) {
if (getTargetFragment() == null)
return;
getTargetFragment().onActivityResult(getTargetRequestCode(),
resultCode, null);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View v = getActivity().getLayoutInflater().inflate(
R.layout.dialog_slideshow_settings, null);
mIntervalField = (TextView) v
.findViewById(R.id.slideshowsettings_interval_field);
mIntervalSeekBar = (SeekBar) v
.findViewById(R.id.slideshowsettings_interval_seekbar);
int maxIntervalInMsec = Settings.get(getActivity())
.getMaxSlideShowInterval();
int maxInterval = maxIntervalInMsec / MSEC_IN_SEC * 2;
mIntervalSeekBar.setMax(maxInterval);
int intervalInMsec = Settings.get(getActivity()).getSlideShowInterval();
int interval = intervalInMsec / MSEC_IN_SEC * 2;
mIntervalSeekBar.setProgress(interval);
mIntervalField.setText(": " + interval * mStep + " ");
mIntervalSeekBar
.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar arg0, int progress,
boolean arg2) {
mIntervalField.setText(": " + progress * mStep + " ");
}
@Override
public void onStartTrackingTouch(SeekBar arg0) {
}
@Override
public void onStopTrackingTouch(SeekBar arg0) {
}
});
mFromTheBeginningCheckBox = (CheckBox) v
.findViewById(R.id.slideshowsettings_fromthebeginning_checkbox);
mFromTheBeginningCheckBox.setChecked(Settings.get(getActivity())
.isSlideShowFromTheBeginning());
return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.slideshow_settings)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
Settings.get(getActivity())
.setSlideShowIntervalBySeconds(
mIntervalSeekBar.getProgress());
sendResult(Activity.RESULT_OK);
Settings.get(getActivity())
.setSlideShowFromTheBeginning(
mFromTheBeginningCheckBox
.isChecked());
}
}).create();
}
}
package com.akishinde.flashcards;
import android.support.v4.app.Fragment;
public class TopicsActivity extends SingleFragmentActivity {
@Override
protected Fragment createFragment() {
// TODO Auto-generated method stub
return new TopicsFragment();
}
}
package com.akishinde.flashcards;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
public class TopicsFragment extends Fragment {
private static final String DIALOG_LANGUAGE = "language";
private static final int REQUEST_LANGUAGE = 0;
private static final int REQUEST_CARDPAGER = 1;
private ImageButton mTopicFoodButton;
private ImageButton mTopicAnimalsButton;
private ImageButton mTopicTransportButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_topics, parent, false);
mTopicFoodButton = (ImageButton) v.findViewById(R.id.topic_food_button);
mTopicFoodButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startCardPagerActivityWithExtra(CardLab.TOPIC.Food);
}
});
mTopicAnimalsButton = (ImageButton) v
.findViewById(R.id.topic_animals_button);
mTopicAnimalsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startCardPagerActivityWithExtra(CardLab.TOPIC.Animals);
}
});
mTopicTransportButton = (ImageButton) v
.findViewById(R.id.topic_transport_button);
mTopicTransportButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startCardPagerActivityWithExtra(CardLab.TOPIC.Transport);
}
});
return v;
}
private void startCardPagerActivityWithExtra(CardLab.TOPIC topic) {
Intent i = new Intent(getActivity(), CardPagerActivity.class);
i.putExtra(CardFragment.EXTRA_TOPIC_NAME, topic);
startActivityForResult(i, REQUEST_CARDPAGER);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_topics, menu);
updateMenuSoundItem(menu.findItem(R.id.menu_item_sound));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_language:
FragmentManager fm = getActivity().getSupportFragmentManager();
LanguageChoiceFragment dialog = new LanguageChoiceFragment();
dialog.setTargetFragment(TopicsFragment.this, REQUEST_LANGUAGE);
dialog.show(fm, DIALOG_LANGUAGE);
return true;
case R.id.menu_item_sound:
Settings.get(getActivity()).switchSound();
updateMenuSoundItem(item);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void updateMenuSoundItem(MenuItem item) {
if (Settings.get(getActivity()).isSoundOn()) {
item.setTitle(R.string.sound_on);
item.setIcon(R.drawable.icon_menu_sound_on);
} else {
item.setTitle(R.string.sound_off);
item.setIcon(R.drawable.icon_menu_sound_off);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode!= Activity.RESULT_OK)
return;
if (requestCode == REQUEST_LANGUAGE) {
getActivity().getSupportFragmentManager().beginTransaction()
.detach(this).attach(this).commit();
}
if (requestCode == REQUEST_CARDPAGER) {
getActivity().getSupportFragmentManager().beginTransaction()
.detach(this).attach(this).commit();
}
}
}
package com.akishinde.flashcards;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.DialogFragment;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
public class UserSoundFragment extends DialogFragment {
private static final String TAG = "UserSoundFragment";
public static final String EXTRA_SOUND_FILENAME = "com.akishinde.flashcards.sound_filename";
private boolean mRecordingStarted;
private boolean mPlayingStarted;
private ImageButton mRecordButton;
private MediaRecorder mRecorder;
private String mSoundFileName;
private ImageButton mPlayButton, mDeleteButton;
private static final int RECORDING_MAX_LENGTH_IN_SECONDS = 3;
private void sendResult(int resultCode) {
if (getTargetFragment() == null)
return;
Intent i = new Intent();
i.putExtra(EXTRA_SOUND_FILENAME, mSoundFileName);
getTargetFragment().onActivityResult(getTargetRequestCode(),
resultCode, i);
}
public static UserSoundFragment newInstance(String soundFileName) {
Bundle args = new Bundle();
args.putString(EXTRA_SOUND_FILENAME, soundFileName);
UserSoundFragment fragment = new UserSoundFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mSoundFileName = getArguments().getString(EXTRA_SOUND_FILENAME);
View v = getActivity().getLayoutInflater().inflate(
R.layout.dialog_user_sound, null);
mRecordingStarted = false;
mPlayingStarted = false;
mRecordButton = (ImageButton) v
.findViewById(R.id.user_sound_record_button);
mRecordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mRecordingStarted) {
stopRecording();
} else {
startRecording();
}
}
});
mPlayButton = (ImageButton) v.findViewById(R.id.user_sound_play_button);
mPlayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPlayingStarted)
stopPlaying();
else
playRecordedSound();
}
});
mDeleteButton = (ImageButton) v
.findViewById(R.id.user_sound_delete_button);
mDeleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteRecordedSoundFile();
enablePlayAndDeleteButtons(false);
}
});
enablePlayAndDeleteButtons(mSoundFileName