diff --git a/.gitignore b/.gitignore index 9a8c230..1254948 100644 --- a/.gitignore +++ b/.gitignore @@ -56,4 +56,7 @@ crashlytics-build.properties com_crashlytics_export_strings.xml # Fabric -fabric.properties \ No newline at end of file +fabric.properties + +# Keystore +keystore.properties \ No newline at end of file diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index 719bb8b..0000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43e..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 7ac24c7..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 5d19981..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 54b7b3f..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e3f632e..316dbc6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,37 @@ +def VERSION_MAJOR = 1 +def VERSION_MINOR = 5 +def VERSION_PATCH = 1 +def VERSION_BUILD = 1 +def VERSION_CODE = VERSION_MAJOR * 10000 + VERSION_MINOR * 1000 + VERSION_PATCH * 100 + VERSION_BUILD +def logging = true + apply plugin: 'com.android.application' +def keystorePropertiesFile = rootProject.file("keystore.properties") +def keystoreProperties = new Properties() +keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + + android { compileSdkVersion 26 buildToolsVersion '26.0.2' + + //distinguish apk file name between different versions and builds + applicationVariants.all { variant -> + variant.outputs.each { output -> + output.outputFile = new File( + output.outputFile.parent, + "$project.name-$variant.name-$variant.versionName-${variant.versionName}.apk") + } + } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } defaultConfig { applicationId "cz.muni.fi.pv256.movio2.uco_422678" minSdkVersion 17 @@ -10,12 +39,39 @@ android { versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + //leave only the necessary resources in the apk + resConfigs "sk", "en" + + versionCode VERSION_CODE + versionName "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + debug { + versionNameSuffix ".${VERSION_BUILD}-${VERSION_CODE}" + buildConfigField "boolean", "REPORT_CRASHES", "false" + } + } + productFlavors { + primary { + logging=true + resValue "string", "app_name", "Movio2" + } + secondary { + logging=false + applicationIdSuffix ".secondary" + resValue "string", "app_name", "Movio2(secret flavor)" + } + } + sourceSets.primary{ + res.srcDirs = ['res', 'src/main/res'] + } + sourceSets.secondary{ + res.srcDirs = ['res', 'src/secondary/res'] } } @@ -26,7 +82,15 @@ dependencies { }) compile 'com.android.support:appcompat-v7:26.0.2' compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.android.support:design:26.+' + compile 'com.squareup.okhttp3:okhttp:3.9.1' + compile 'com.google.code.gson:gson:2.8.2' + compile 'com.squareup.picasso:picasso:2.5.2' + compile 'com.squareup.retrofit2:retrofit:2.3.0' + compile 'com.squareup.retrofit2:converter-gson:2.3.0' + compile 'joda-time:joda-time:2.9.9' testCompile 'junit:junit:4.12' + compile "com.android.support:support-core-utils:26.1.0" } allprojects { diff --git a/app/src/androidTest/java/cz/muni/fi/pv256/movio2/MovieManagerTest.java b/app/src/androidTest/java/cz/muni/fi/pv256/movio2/MovieManagerTest.java new file mode 100644 index 0000000..bfd8285 --- /dev/null +++ b/app/src/androidTest/java/cz/muni/fi/pv256/movio2/MovieManagerTest.java @@ -0,0 +1,49 @@ +package cz.muni.fi.pv256.movio2; + +import android.test.AndroidTestCase; +import android.util.Log; +import java.util.ArrayList; + +import cz.muni.fi.pv256.movio2.uco_422678.Movie; +import cz.muni.fi.pv256.movio2.uco_422678.MovieContract; +import cz.muni.fi.pv256.movio2.uco_422678.MovieDBHelper; +import cz.muni.fi.pv256.movio2.uco_422678.MovieManagerImpl; + +/** + * Created by BranislavSmik on 1/11/2018. + */ + +public class MovieManagerTest extends AndroidTestCase { + private static final String TAG = MovieManagerImpl.class.getSimpleName(); + + private MovieManagerImpl mManager; + + @Override + protected void setUp() throws Exception { + mManager = new MovieManagerImpl((new MovieDBHelper(mContext)).getWritableDatabase()); + } + + @Override + public void tearDown() throws Exception { + mContext.getContentResolver().delete( + MovieContract.MovieEntry.CONTENT_URI, + null, + null + ); + } + + public void testAddMovie() throws Exception { + Movie expected = createMovie("testTitle", 1500260033l, "test-cover", "test-backdrop", 5.3f , "description of the test"); + mManager.createMovie(expected); + ArrayList saved = (ArrayList)mManager.getSavedMovies(); + + Log.d(TAG, saved.get(0).toString()); + assertTrue(saved.size() == 1); + assertEquals(expected, saved.get(0)); + } + + private Movie createMovie(String title, Long release, String cover, String backdrop, float rating, String overview) { + Movie m = new Movie(123l, release, cover, backdrop, title, overview, rating); + return m; + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0199761..e7a35c1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,14 @@ + package="cz.muni.fi.pv256.movio2"> + + + + + + + + - + android:theme="@style/SecondTheme" + android:name=".App"> + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/App.java b/app/src/main/java/cz/muni/fi/pv256/movio2/App.java index 0c93b1c..a34aa74 100644 --- a/app/src/main/java/cz/muni/fi/pv256/movio2/App.java +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/App.java @@ -17,6 +17,7 @@ public void onCreate() { } } + private void initStrictMode() { StrictMode.ThreadPolicy.Builder tpb = new StrictMode.ThreadPolicy.Builder() .detectAll() diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/AbstractDataLoader.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/AbstractDataLoader.java new file mode 100644 index 0000000..bf7eb07 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/AbstractDataLoader.java @@ -0,0 +1,106 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import java.util.List; +import android.content.Context; +import android.support.v4.content.AsyncTaskLoader; + +/** + * Created by BranislavSmik on 12/13/2017. + */ + +public abstract class AbstractDataLoader> extends + AsyncTaskLoader { + protected E mLastDataList = null; + protected abstract E buildList(); + public AbstractDataLoader(Context context) { + super(context); + } + /** + * Runs on a worker thread, loading in our data. Delegates the real work to + * concrete subclass' buildList() method. + */ + @Override + public E loadInBackground() { + return buildList(); + } + /** + * Runs on the UI thread, routing the results from the background thread to + * whatever is using the dataList. + */ + @Override + public void deliverResult(E dataList) { + if (isReset()) { + // An async query came in while the loader is stopped + emptyDataList(dataList); + return; + } + E oldDataList = mLastDataList; + mLastDataList = dataList; + if (isStarted()) { + super.deliverResult(dataList); + } + if (oldDataList != null && oldDataList != dataList + && oldDataList.size() > 0) { + emptyDataList(oldDataList); + } + } + /** + * Starts an asynchronous load of the list data. When the result is ready + * the callbacks will be called on the UI thread. If a previous load has + * been completed and is still valid the result may be passed to the + * callbacks immediately. + * + * Must be called from the UI thread. + */ + @Override + protected void onStartLoading() { + if (mLastDataList != null) { + deliverResult(mLastDataList); + } + if (takeContentChanged() || mLastDataList == null + || mLastDataList.size() == 0) { + forceLoad(); + } + } + /** + * Must be called from the UI thread, triggered by a call to stopLoading(). + */ + @Override + protected void onStopLoading() { + // Attempt to cancel the current load task if possible. + cancelLoad(); + } + /** + * Must be called from the UI thread, triggered by a call to cancel(). Here, + * we make sure our Cursor is closed, if it still exists and is not already + * closed. + */ + @Override + public void onCanceled(E dataList) { + if (dataList != null && dataList.size() > 0) { + emptyDataList(dataList); + } + } + /** + * Must be called from the UI thread, triggered by a call to reset(). Here, + * we make sure our Cursor is closed, if it still exists and is not already + * closed. + */ + @Override + protected void onReset() { + super.onReset(); + // Ensure the loader is stopped + onStopLoading(); + if (mLastDataList != null && mLastDataList.size() > 0) { + emptyDataList(mLastDataList); + } + mLastDataList = null; + } + protected void emptyDataList(E dataList) { + if (dataList != null && dataList.size() > 0) { + for (int i = 0; i < dataList.size(); i++) { + dataList.remove(i); + } + } + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Constants.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Constants.java new file mode 100644 index 0000000..aa198a2 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Constants.java @@ -0,0 +1,15 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +/** + * Created by BranislavSmik on 11/26/2017. + */ + +public class Constants { + public static final String APIKEYv3 = "80606f918fbd89549fcbb4a0e2782925"; + public static final String APIKEYv4 = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI4MDYwNmY5MThmYmQ4OTU0OWZjYmI0YTBlMjc4MjkyNSIsInN1YiI6IjU5ZDU0NDg4YzNhMzY4NDU3MjAxYmFjYiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.KCTMoy6eS9chRIUFdGV0KYy6d7Ebx2seEtHL4mrEzHg"; + + public static final String QUERY_PARAM_INTHEATRES = "&primary_release_date.gte=2018-01-01&primary_release_date.lte=2018-01-31"; //Just MVP solution + public static final String QUERY_PARAM_DRAMA = "&with_genres=18&sort_by=popularity.desc"; + public static final String URL = "http://api.themoviedb.org/"; + public static final String PATH = "3/discover/movie?api_key="; +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/ContentChangingTask.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/ContentChangingTask.java new file mode 100644 index 0000000..4be19c4 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/ContentChangingTask.java @@ -0,0 +1,22 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.os.AsyncTask; +import android.support.v4.content.Loader; + + +/** + * Created by BranislavSmik on 12/13/2017. + */ +abstract class ContentChangingTask extends + AsyncTask { + private Loader loader=null; + + ContentChangingTask(Loader loader) { + this.loader=loader; + } + + @Override + protected void onPostExecute(T3 param) { + loader.onContentChanged(); + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/DetailFragment.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/DetailFragment.java new file mode 100644 index 0000000..28b4c60 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/DetailFragment.java @@ -0,0 +1,106 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.database.sqlite.SQLiteDatabase; +import java.text.SimpleDateFormat; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.ImageView; +import android.content.Context; +import android.widget.Toast; + +import cz.muni.fi.pv256.movio2.R; +import com.squareup.picasso.Picasso; + + +/** + * Created by BranislavSmik on 10/19/2017. + */ + +public class DetailFragment extends Fragment { + public static final String TAG = DetailFragment.class.getSimpleName(); + private static final String ARGS_MOVIE = "args_movie"; + public static final String DATE_FORMAT = "dd.MM.yyyy"; + + private Context mContext; + private Movie mMovie; + + private SQLiteDatabase mDatabase; + private MovieManagerImpl mFilmManager; + private MovieDBHelper mDbHelper; + + public static DetailFragment newInstance(Movie movie) { + DetailFragment fragment = new DetailFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARGS_MOVIE, movie); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = getActivity(); + Bundle args = getArguments(); + if (args != null) { + mMovie = args.getParcelable(ARGS_MOVIE); + } + + mDbHelper = new MovieDBHelper(getActivity()); + mDatabase = mDbHelper.getWritableDatabase(); + mFilmManager = new MovieManagerImpl(mDatabase); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_detail, container, false); + + TextView titleTextView = (TextView) view.findViewById(R.id.movie_title); + TextView ratingTextView = (TextView) view.findViewById(R.id.movie_rating); + TextView descriptionTextView = (TextView) view.findViewById(R.id.movie_description); + TextView releaseDateTextView = (TextView) view.findViewById(R.id.movie_release); + ImageView movieImage = (ImageView) view.findViewById(R.id.movie_image); + + final FloatingActionButton floatingActionButton = (FloatingActionButton) view.findViewById(R.id.float_but); + floatingActionButton.setImageResource(R.drawable.plus); + + if (mFilmManager.containsMovie(mMovie.getId())) { + floatingActionButton.setImageResource(R.drawable.minus); + } + + floatingActionButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mFilmManager.containsMovie(mMovie.getId())) { + mFilmManager.deleteMovie(mMovie); + Toast.makeText(getActivity(), mMovie.getTitle() + " removed from favorites.", Toast.LENGTH_LONG).show(); + floatingActionButton.setImageResource(R.drawable.plus); + } else { + mFilmManager.createMovie(mMovie); + Toast.makeText(getActivity(), mMovie.getTitle() + " added to favorites.", Toast.LENGTH_LONG).show(); + floatingActionButton.setImageResource(R.drawable.minus); + } + + } + }); + + if (mMovie != null) { + titleTextView.setText(mMovie.getTitle()); + ratingTextView.setText(Float.toString(mMovie.getPopularity())); + descriptionTextView.setText(mMovie.getOverview()); + Picasso.with(mContext).load("https://image.tmdb.org/t/p/w500/" + mMovie.getBackdrop()).into(movieImage); + + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + releaseDateTextView.setText(dateFormat.format(mMovie.getRealeaseDate())); + } + + return view; + } + +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/DownloadService.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/DownloadService.java new file mode 100644 index 0000000..7819a5c --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/DownloadService.java @@ -0,0 +1,157 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.app.Notification; +import android.app.NotificationManager; +import android.support.annotation.Nullable; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v4.app.NotificationCompat; +import android.content.Intent; +import android.content.Context; +import android.util.MalformedJsonException; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; + +import cz.muni.fi.pv256.movio2.R; +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +/** + * Created by BranislavSmik on 12/1/2017. + */ + +public class DownloadService extends IntentService { + public static final String ACTION_NEW = "download_new_movies"; + public static final String ACTION_DRAMA = "download_drama_movies"; + public static final String ACTION = "download_action"; + public static final String ERROR = "download_error"; + public static final String ERROR_PARSE = "parsing_error"; + public static final String ERROR_CONN = "connection_error"; + public static final String RESPONSE = "response"; + public static final String INTENT = "DonwloadService"; + public static final String CHANNEL = "download_notify"; + + private MovieAPI APIservice; + + public DownloadService() { + super(DownloadService.class.getSimpleName()); + } + + public DownloadService(String name) { + super(name); + } + + @Override + public void onCreate() { + super.onCreate(); + APIservice = getMovieAPI(); + } + + @Override + protected void onHandleIntent(Intent intent) { + String action = intent.getAction(); + downloadingNotification(); + + try { + if(action == ACTION_NEW) { + Call requestNew = APIservice.getMovieList(Constants.QUERY_PARAM_INTHEATRES); + MovieList newMovies = requestNew.execute().body(); + broadcastMovieList(new ArrayList(newMovies.getResults()), action); + + } else if(action == ACTION_DRAMA) { + Call requestDrama = APIservice.getMovieList(Constants.QUERY_PARAM_DRAMA); + MovieList dramaMovies = requestDrama.execute().body(); + broadcastMovieList(new ArrayList(dramaMovies.getResults()), action); + } + } catch (MalformedJsonException e) { + broadcastMovieListError(ERROR_PARSE); + e.printStackTrace(); + return; + } catch (IOException e) { + broadcastMovieListError(ERROR_CONN); + e.printStackTrace(); + return; + } + + downloadingEndNotification(); + } + + private void broadcastMovieList(ArrayList movieList, String action) { + Intent broadcastIntent = new Intent(INTENT); + broadcastIntent.putExtra(ACTION, action); + broadcastIntent.putExtra(RESPONSE, movieList); + LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent); + } + + private void broadcastMovieListError(String error) { + Intent broadcastIntent = new Intent(INTENT); + broadcastIntent.putExtra(ERROR, error); + downloadingErrorNotification(error); + LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent); + } + + private MovieAPI getMovieAPI() { + MovieAPI sMovieAPI; + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(Constants.URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + sMovieAPI= retrofit.create(MovieAPI.class); + + return sMovieAPI; + } + + public void downloadingErrorNotification(String error) { + Notification.Builder n = prepareNotification(); + + if(error == ERROR_CONN) { + n.setContentText(getResources().getText(R.string.no_data_warning)).setSmallIcon(R.mipmap.ic_download); + } else if(error == ERROR_PARSE) { + n.setContentText(getResources().getText(R.string.parse_error)).setSmallIcon(R.mipmap.ic_download); + } + + NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + notificationManager.notify(0, n.build()); //0 udává číslo notifikace. Na některých zařízeních nefunguje jinačí int než 0. + } + + private void downloadingNotification() { + Notification.Builder n = prepareNotification(); + n.setContentText("Downloading movies").setSmallIcon(R.mipmap.ic_download); + + NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + notificationManager.notify(0, n.build()); //0 udává číslo notifikace. Na některých zařízeních nefunguje jinačí int než 0. + } + + private void downloadingEndNotification() { + Notification.Builder n = prepareNotification(); + n.setContentText("Movies downloaded successfully").setSmallIcon(R.mipmap.ic_download); + + NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + notificationManager.notify(0, n.build()); //0 udává číslo notifikace. Na některých zařízeních nefunguje jinačí int než 0. + } + + private Notification.Builder prepareNotification() { + Intent intent = new Intent(this, MovieListFragment.class); + PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + Notification.Builder n; + if (Build.VERSION.SDK_INT < 26) { + n = new Notification.Builder(this); + } + else { + n = new Notification.Builder(this, CHANNEL); + } + n.setContentTitle(getResources().getString(R.string.app_name)) + .setContentIntent(pIntent) + .setAutoCancel(true).build(); + return n; + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MainActivity.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MainActivity.java new file mode 100644 index 0000000..82fc910 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MainActivity.java @@ -0,0 +1,118 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v4.app.FragmentManager; +import android.support.v7.widget.SwitchCompat; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.CompoundButton; + +import cz.muni.fi.pv256.movio2.R; +import cz.muni.fi.pv256.movio2.uco_422678.sync.UpdaterSyncAdapter; + +public class MainActivity extends AppCompatActivity implements MovieListFragment.OnMovieSelectListener { + //private RecyclerView mRecyclerView; + //private RecyclerView.Adapter mAdapter; + //private RecyclerView.LayoutManager mLayoutManager; + //private static final String PREFSTRING = "currentTheme"; + //protected static ArrayList mMovieList; + private boolean mTwoPane; + private SwitchCompat mSwitchButton; + private Toolbar toolbar; + protected MovieListFragment mListFragment; + private static final String TAG = MainActivity.class.getSimpleName(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + if (findViewById(R.id.movie_detail_container) != null) { + // The detail container view will be present only in the large-screen layouts + // (res/layout-sw600dp). If this view is present, then the activity should be + // in two-pane mode. + mTwoPane = true; + // In two-pane mode, show the detail view in this activity by + // adding or replacing the detail fragment using a + // fragment transaction. + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.movie_detail_container, new DetailFragment(), DetailFragment.TAG) + .commit(); + } + } else { + mTwoPane = false; + getSupportActionBar().setElevation(0f); + } + + UpdaterSyncAdapter.initializeSyncAdapter(this); + } + + @Override + public void onMovieSelect(Movie movie) { + if (mTwoPane) { + FragmentManager myManager = getSupportFragmentManager(); + + DetailFragment fragment = DetailFragment.newInstance(movie); + myManager.beginTransaction() + .replace(R.id.movie_detail_container, fragment, DetailFragment.TAG) + .commit(); + + } else { + Intent intent = new Intent(this, MovieDetailActivity.class); + intent.putExtra(MovieDetailActivity.EXTRA_MOVIE, movie); + startActivity(intent); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu, menu); + MenuItem item = menu.findItem(R.id.menuSwitch); + item.setActionView(R.layout.menu_switch); + mSwitchButton = item.getActionView().findViewById(R.id.switchForActionBar); + mSwitchButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + if (compoundButton.isChecked()) { + compoundButton.setText("Favourites"); + compoundButton.setChecked(true); + mListFragment = MovieListFragment.newInstance(true); + } else { + mListFragment.setMenuVisibility(false); + compoundButton.setText("Discover"); + compoundButton.setChecked(false); + mListFragment = MovieListFragment.newInstance(false); + } + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragment_main, mListFragment) + .commit(); + + } + }); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.synchronize: + Log.d(TAG, "sync button clicked"); + UpdaterSyncAdapter.syncImmediately(getApplicationContext()); + return true; + + default: + // If we got here, the user's action was not recognized. + // Invoke the superclass to handle it. + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onPointerCaptureChanged(boolean hasCapture) { + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Movie.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Movie.java new file mode 100644 index 0000000..adc4296 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Movie.java @@ -0,0 +1,115 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by BranislavSmik on 10/19/2017. + */ + +public class Movie implements Parcelable{ + + private Long mId; + private Long mRealeaseDate; + private String mCoverPath; + private String mTitle; + private String mBackdrop; + private String mOverview; + private Float mPopularity; + + public Movie(Long id, Long realeaseDate, String coverPath, String backdrop, String title, String overview, Float popularity) { + mId = id; + mRealeaseDate = realeaseDate; + mCoverPath = coverPath; + mTitle = title; + mBackdrop = backdrop; + mOverview = overview; + mPopularity = popularity; + } + + public Long getId() { return mId; } + + public void setId(Long id) { mId = id; } + + public Long getRealeaseDate() { + return mRealeaseDate; + } + + public void setRealeaseDate(Long realeaseDate) { + mRealeaseDate = realeaseDate; + } + + public String getCoverPath() { + return mCoverPath; + } + + public void setCoverPath(String coverPath) { + mCoverPath = coverPath; + } + + public String getTitle() { + return mTitle; + } + + public void setTitle(String title) { + mTitle = title; + } + + public String getBackdrop() { + return mBackdrop; + } + + public void setBackdrop(String backdrop) { + mBackdrop = backdrop; + } + + public Float getPopularity() { + return mPopularity; + } + + public void setPopularity(Float popularity) { + mPopularity = popularity; + } + + public void setOverview(String overview){ mOverview = overview;} + + public String getOverview() { return mOverview;} + + @Override + public int describeContents() { + return 0; + } + + public Movie(Parcel in) { + mId = in.readLong(); + mRealeaseDate = in.readLong(); + mCoverPath = in.readString(); + mTitle = in.readString(); + mBackdrop = in.readString(); + mOverview = in.readString(); + mPopularity = in.readFloat(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mId); + dest.writeLong(mRealeaseDate); + dest.writeString(mCoverPath); + dest.writeString(mTitle); + dest.writeString(mBackdrop); + dest.writeString(mOverview); + dest.writeFloat(mPopularity); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Movie createFromParcel(Parcel sourceParcel) { + return new Movie(sourceParcel); + } + + @Override + public Movie[] newArray(int size) { + return new Movie[size]; + } + }; +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieAPI.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieAPI.java new file mode 100644 index 0000000..06d1a07 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieAPI.java @@ -0,0 +1,20 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import java.util.ArrayList; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.Query; + +/** + * Created by BranislavSmik on 12/1/2017. + */ + +public interface MovieAPI { + @GET(Constants.PATH + Constants.APIKEYv3) //+ "{query_param}" + Call getMovieList(@Query("query_param") String query_param); + + @GET("3/movie/{id}?api_key=" + Constants.APIKEYv3) + Call getMovieById(@Path("id") Long id); +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieContract.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieContract.java new file mode 100644 index 0000000..5fde7b4 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieContract.java @@ -0,0 +1,62 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.content.ContentUris; +import android.net.Uri; +import android.provider.BaseColumns; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Created by BranislavSmik on 12/7/2017. + */ +public class MovieContract { + + public static final String CONTENT_AUTHORITY = "cz.muni.fi.pv256.movio2.uco_422678"; + public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY); + public static final String PATH_MOVIES = "movies"; + + public static final String DATE_FORMAT = "yyyyMMdd"; + + public static String insertDateToDb(Long longDate){ + SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT); + return formatter.format(longDate); + } + + public static Long getDateFromDb(String dBdate){ + SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT); + Date date = null; + try { + date = (Date)formatter.parse(dBdate); + } catch (ParseException e) { + e.printStackTrace(); + } + return date.getTime(); + } + + public static final class MovieEntry implements BaseColumns { + + public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_MOVIES).build(); + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + PATH_MOVIES; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + PATH_MOVIES; + + public static final String MOVIE_TABLE_NAME = "movies"; + + public static final String COLUMN_TITLE = "title"; + public static final String COLUMN_OVERVIEW = "overview"; + public static final String COLUMN_RELEASE_DATE = "release_date"; + public static final String COLUMN_COVER_PATH = "cover_path"; + public static final String COLUMN_BACKDROP_PATH = "backdrop_path"; + public static final String COLUMN_POPULARITY = "popularity"; + + public static Uri buildMovieUri(long id) { + return ContentUris.withAppendedId(CONTENT_URI, id); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDBHelper.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDBHelper.java new file mode 100644 index 0000000..53b5b7f --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDBHelper.java @@ -0,0 +1,42 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import cz.muni.fi.pv256.movio2.uco_422678.MovieContract.*; + +/** + * Created by BranislavSmik on 12/10/2017. + */ + +public class MovieDBHelper extends SQLiteOpenHelper { + + public static final String DATABASE_NAME = "movies.db"; + private static final int DATABASE_VERSION = 1; + + public MovieDBHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + final String SQL_CREATE_MOVIE_TABLE = "CREATE TABLE " + MovieEntry.MOVIE_TABLE_NAME + " (" + + MovieEntry._ID + " INTEGER PRIMARY KEY," + + MovieEntry.COLUMN_RELEASE_DATE + " TEXT NOT NULL, " + + MovieEntry.COLUMN_COVER_PATH + " TEXT NOT NULL, " + + MovieEntry.COLUMN_TITLE + " TEXT NOT NULL, " + + MovieEntry.COLUMN_BACKDROP_PATH + " TEXT, " + + MovieEntry.COLUMN_OVERVIEW + " TEXT, " + + MovieEntry.COLUMN_POPULARITY + " TEXT" + + " );"; + + db.execSQL(SQL_CREATE_MOVIE_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + MovieEntry.MOVIE_TABLE_NAME); + onCreate(db); + } +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDTO.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDTO.java new file mode 100644 index 0000000..c2f2c42 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDTO.java @@ -0,0 +1,79 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +/** + * Created by BranislavSmik on 11/27/2017. + */ + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.google.gson.annotations.SerializedName; + +public class MovieDTO { + @SerializedName("id") + private Long mId; + @SerializedName("release_date") + private String mRealeaseDate; + @SerializedName("poster_path") + private String mCoverPath; + @SerializedName("title") + private String mTitle; + @SerializedName("vote_average") + private String mPopularity; + @SerializedName("backdrop_path") + private String mBackdrop; + @SerializedName("overview") + private String mOverview; + + + public MovieDTO(Long id, String realeaseDate, String coverPath, String backdrop, String title, String overview, String popularity) { + mId = id; + mRealeaseDate = realeaseDate; + mCoverPath = coverPath; + mTitle = title; + mBackdrop = backdrop; + mOverview = overview; + mPopularity = popularity; + } + + public Long getId() { return mId; } + + public String getRealeaseDate() { + return mRealeaseDate; + } + + public Long getRealeaseDateAsLong() { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + Date date = null; + try { + date = (Date)formatter.parse(getRealeaseDate()); + } catch (ParseException e) { + e.printStackTrace(); + } + return date.getTime(); + } + + public String getCoverPath() { + return mCoverPath; + } + + public String getTitle() { + return mTitle; + } + + public String getBackdrop() { + return mBackdrop; + } + + public String getPopularity() { + return mPopularity; + } + + public String getOverview() {return mOverview;} + + public Float getPopularityAsFloat() { + return Float.parseFloat(getPopularity()); + } +} + diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDataLoader.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDataLoader.java new file mode 100644 index 0000000..d42a93b --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDataLoader.java @@ -0,0 +1,69 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import java.util.List; +import android.content.Context; + +/** + * Created by BranislavSmik on 12/13/2017. + */ + +public class MovieDataLoader extends AbstractDataLoader> { + + private MovieManager mMovieManager; + private String mSelection; + private String[] mSelectionArgs; + private String mGroupBy; + private String mHaving; + private String mOrderBy; + + public MovieDataLoader(Context context, MovieManager movieManager, String selection, String[] selectionArgs, + String groupBy, String having, String orderBy) { + super(context); + mMovieManager = movieManager; + mSelection = selection; + mSelectionArgs = selectionArgs; + mGroupBy = groupBy; + mHaving = having; + mOrderBy = orderBy; + } + + @Override + protected List buildList() { + List movieList = mMovieManager.getSavedMovies(); + return movieList; + } + + public void create(Movie movie) { + new InsertTask(this).execute(movie); + } + + public void delete(Movie movie) { + new DeleteTask(this).execute(movie); + } + + + private class InsertTask extends ContentChangingTask { + InsertTask(MovieDataLoader loader) { + super(loader); + } + + @Override + protected Void doInBackground(Movie... params) { + mMovieManager.createMovie(params[0]); + return (null); + } + } + + + private class DeleteTask extends ContentChangingTask { + DeleteTask(MovieDataLoader loader) { + super(loader); + } + + @Override + protected Void doInBackground(Movie... params) { + mMovieManager.deleteMovie(params[0]); + return (null); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDetailActivity.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDetailActivity.java new file mode 100644 index 0000000..05cd4e8 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieDetailActivity.java @@ -0,0 +1,47 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import cz.muni.fi.pv256.movio2.R; + +/** + * Created by BranislavSmik on 10/19/2017. + */ + +public class MovieDetailActivity extends AppCompatActivity{ + public static final String EXTRA_MOVIE = "extra_movie"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_detail); + + /* + Toolbar toolbar = (Toolbar) findViewById(R.id.description_toolbar); + setSupportActionBar(toolbar); + + if (getSupportActionBar() != null){ + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + } + */ + + if(savedInstanceState == null){ + Movie movie = getIntent().getParcelableExtra(EXTRA_MOVIE); + FragmentManager fm = getSupportFragmentManager(); + DetailFragment fragment = (DetailFragment) fm.findFragmentById(R.id.movie_detail_container); + + if (fragment == null) { + fragment = DetailFragment.newInstance(movie); + fm.beginTransaction() + .add(R.id.movie_detail_container, fragment) + .commit(); + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieList.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieList.java new file mode 100644 index 0000000..9091512 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieList.java @@ -0,0 +1,15 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import java.util.ArrayList; + +/** + * Created by BranislavSmik on 12/2/2017. + */ + +public class MovieList { + public ArrayList results; + + public ArrayList getResults() { + return results; + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieListFragment.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieListFragment.java new file mode 100644 index 0000000..cee9850 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieListFragment.java @@ -0,0 +1,256 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.DownloadListener; +import android.widget.ListView; +import android.widget.Button; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import java.util.ArrayList; +import android.os.AsyncTask; +import android.os.Build; +import android.view.ViewStub; +import java.io.IOException; +import java.util.List; + +import android.widget.Toast; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.app.NotificationChannel; +import android.app.NotificationManager; + +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SwitchCompat; + +import cz.muni.fi.pv256.movio2.R; +import cz.muni.fi.pv256.movio2.uco_422678.DownloadService; + +/** + * Created by BranislavSmik on 10/19/2017. + */ + +public class MovieListFragment extends Fragment implements + LoaderManager.LoaderCallbacks>{ + private static final String TAG = MovieListFragment.class.getSimpleName(); + private static final String SELECTED_KEY = "selected_position"; + + private int mPosition = ListView.INVALID_POSITION; + private OnMovieSelectListener mListener; + private Context mContext; + private RecyclerView mRecyclerView; + protected RecyclerAdapter mRecyclerAdapter; + protected RecyclerView.LayoutManager mLayoutManager; + private ViewStub mEmptyView; + private MovieBroadcastReceiver mReceiver; + + private ArrayList items = new ArrayList<>(); + + private MovieManagerImpl mMovieManager; + private SQLiteDatabase mDatabase; + private MovieDBHelper mDbHelper; + private static final int LOADER_ID = 1; + private boolean isSwitchedToFav; + + public static MovieListFragment newInstance(Boolean isChecked) { + Bundle args = new Bundle(); + args.putBoolean("isSwitchedToFav", isChecked); + MovieListFragment fragment = new MovieListFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onAttach(Context activity) { + super.onAttach(activity); + + mListener = (OnMovieSelectListener) activity; + } + + @Override + public void onDetach() { + super.onDetach(); + + //Avoid Activity leaking + mListener = null; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null) { + isSwitchedToFav = getArguments().getBoolean("isSwitchedToFav") ? true : false; + } + else { + isSwitchedToFav = false; + } + + mContext = getActivity().getApplicationContext(); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_movie_list , container, false); + //mEmptyView = (ViewStub) view.findViewById(R.id.nodata_textview); + + if (savedInstanceState != null && savedInstanceState.containsKey(SELECTED_KEY)) { + mPosition = savedInstanceState.getInt(SELECTED_KEY); + } + + mRecyclerView = (RecyclerView) view.findViewById(R.id.my_recycler_view); + initChannels(getActivity()); + mRecyclerAdapter = new RecyclerAdapter(getContext(), new ArrayList<>()); + mRecyclerView.setAdapter(mRecyclerAdapter); + mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + if (isSwitchedToFav) { + mDbHelper = new MovieDBHelper(getActivity()); + mDatabase = mDbHelper.getWritableDatabase(); + mMovieManager = new MovieManagerImpl(mDatabase); + getLoaderManager().initLoader(LOADER_ID, null, this); + } else { + if (!isConnected(this.getActivity())) { + Toast.makeText(getActivity(), "NO CONNECTION", Toast.LENGTH_LONG).show(); + } else { + if (savedInstanceState != null && savedInstanceState.containsKey(SELECTED_KEY)) { + mPosition = savedInstanceState.getInt(SELECTED_KEY); + } + mDbHelper = new MovieDBHelper(getContext()); + mDatabase = mDbHelper.getWritableDatabase(); + mMovieManager = new MovieManagerImpl(mDatabase); + + Intent intent = new Intent(getActivity(), DownloadService.class); + intent.setAction(DownloadService.ACTION_NEW); + getActivity().startService(intent); + + intent = new Intent(getActivity(), DownloadService.class); + intent.setAction(DownloadService.ACTION_DRAMA); + getActivity().startService(intent); + + mReceiver = new MovieBroadcastReceiver(); + IntentFilter intentFilter = new IntentFilter(DownloadService.INTENT); + LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mReceiver, intentFilter); + } + } + + return view; + } + + + @Override + public void onSaveInstanceState(Bundle outState) { + // When tablets rotate, the currently selected list item needs to be saved. + // When no item is selected, mPosition will be set to Listview.INVALID_POSITION, + // so check for that before storing. + if (mPosition != ListView.INVALID_POSITION) { + outState.putInt(SELECTED_KEY, mPosition); + } + super.onSaveInstanceState(outState); + } + + public interface OnMovieSelectListener { + void onMovieSelect(Movie movie); + } + + @Override + public void onResume() { + super.onResume(); + IntentFilter intentFilter = new IntentFilter(DownloadService.INTENT); + mReceiver = new MovieBroadcastReceiver(); + getActivity().registerReceiver(mReceiver, intentFilter); + } + + @Override + public void onPause() { + super.onPause(); + getActivity().unregisterReceiver(mReceiver); + } + + public class MovieBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String error = intent.getStringExtra(DownloadService.RESPONSE); + if (error != null) { + switch (error) { + case DownloadService.ERROR_CONN: + mRecyclerView.setVisibility(View.GONE); + mEmptyView.setVisibility(View.VISIBLE); + break; + case DownloadService.ERROR_PARSE: + mRecyclerView.setVisibility(View.GONE); + mEmptyView.setVisibility(View.VISIBLE); + break; + } + } + + String action = intent.getStringExtra(DownloadService.ACTION); + ArrayList movieList = (ArrayList) intent.getSerializableExtra(DownloadService.RESPONSE); + if(action == DownloadService.ACTION_NEW) { + //items = new ArrayList<>(); + items.add("In theatres now"); + addMovies(movieList); + + if (mRecyclerAdapter != null) mRecyclerAdapter.dataUpdate(items); + } else if(action == DownloadService.ACTION_DRAMA) { + //items = new ArrayList<>(); + items.add("Drama movies"); + addMovies(movieList); + + if (mRecyclerAdapter != null) mRecyclerAdapter.dataUpdate(items); + } + } + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + MovieDataLoader loader = new MovieDataLoader(getActivity(), mMovieManager, null, null, null, null, null); + return loader; + } + @Override + public void onLoadFinished(Loader> loader, List data) { + items.clear(); + items.addAll(data); + mRecyclerAdapter.dataUpdate(items); + } + @Override + public void onLoaderReset(Loader> arg0) { + } + + public static boolean isConnected(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + } + + private void addMovies(ArrayList movieList){ + for (MovieDTO m : movieList) { + Movie movie = new Movie(m.getId(), m.getRealeaseDateAsLong(), m.getCoverPath(), m.getBackdrop(), m.getTitle(), m.getOverview(), m.getPopularityAsFloat()); + items.add(movie); + } + } + + public void initChannels(Context context) { + if (Build.VERSION.SDK_INT < 26) { + return; + } + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationChannel channel = new NotificationChannel(DownloadService.CHANNEL, "Download", NotificationManager.IMPORTANCE_LOW); + notificationManager.createNotificationChannel(channel); + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieManager.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieManager.java new file mode 100644 index 0000000..0ff81da --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieManager.java @@ -0,0 +1,27 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import java.util.List; + +/** + * Created by BranislavSmik on 12/7/2017. + */ + +public abstract class MovieManager { + + protected SQLiteDatabase mDatabase; + + public MovieManager(SQLiteDatabase database) { + mDatabase = database; + } + + public abstract void createMovie(Movie movie); + //public abstract void updateMovie(Movie movie); + public abstract void deleteMovie(Movie movie); + public abstract Movie getMovie(Cursor cursor); + public abstract List getSavedMovies(); + public abstract boolean containsMovie(Long id); +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieManagerImpl.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieManagerImpl.java new file mode 100644 index 0000000..561b542 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieManagerImpl.java @@ -0,0 +1,169 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import cz.muni.fi.pv256.movio2.BuildConfig; + +/** + * Created by BranislavSmik on 12/7/2017. + */ + +public class MovieManagerImpl extends MovieManager{ + + public static final int COL_MOVIE_ID = 0; + public static final int COL_MOVIE_RELEASE_DATE = 1; + public static final int COL_MOVIE_COVER_PATH = 2; + public static final int COL_MOVIE_TITLE = 3; + public static final int COL_MOVIE_BACKDROP_PATH = 4; + public static final int COL_MOVIE_OVERVIEW = 5; + public static final int COL_MOVIE_POPULARITY = 6; + + private static final String[] MOVIE_COLUMNS = { + MovieContract.MovieEntry._ID, + MovieContract.MovieEntry.COLUMN_RELEASE_DATE, + MovieContract.MovieEntry.COLUMN_COVER_PATH, + MovieContract.MovieEntry.COLUMN_TITLE, + MovieContract.MovieEntry.COLUMN_BACKDROP_PATH, + MovieContract.MovieEntry.COLUMN_OVERVIEW, + MovieContract.MovieEntry.COLUMN_POPULARITY, + }; + + //String WHERE_ID = MovieContract.MovieEntry._ID + " = ?"; + + public MovieManagerImpl(SQLiteDatabase database) { + super(database); + } + + @Override + public void createMovie(Movie movie) { + if (movie == null) { + throw new NullPointerException("movie is null"); + } + if (movie.getTitle() == null) { + throw new IllegalStateException("movie title is null"); + } + if (movie.getCoverPath() == null) { + throw new IllegalStateException("movie coverpath is null"); + } + +// movie.setId(ContentUris.parseId(mContext.getContentResolver().insert(MovieContract.MovieEntry.CONTENT_URI, +// prepareMovieValues(movie)))); + mDatabase.insert(MovieContract.MovieEntry.MOVIE_TABLE_NAME, null, prepareMovieValues(movie)); + } + + + @Override + public void deleteMovie(Movie movie) { + if (movie == null) { + throw new NullPointerException("movie is nulll"); + } + if (movie.getId() == null) { + throw new IllegalStateException("movie id is null"); + } + +// mContext.getContentResolver().delete(MovieContract.MovieEntry.CONTENT_URI, +// MovieContract.MovieEntry._ID + " = ?", new String[]{String.valueOf(movie.getId())}); + mDatabase.delete(MovieContract.MovieEntry.MOVIE_TABLE_NAME, MovieContract.MovieEntry._ID + " = " + movie.getId(), null); + } + + /* + @Override + public void updateMovie(Movie movie) { + if (movie == null) { + throw new NullPointerException("movie is null"); + } + if (movie.getTitle() == null) { + throw new IllegalStateException("movie title is null"); + } + if (movie.getCoverPath() == null) { + throw new IllegalStateException("movie coverpath is null"); + } + if (movie.getId() == null) { + throw new IllegalStateException("movie id is null"); + } + + mContext.getContentResolver().update(MovieContract.MovieEntry.CONTENT_URI, prepareMovieValues(movie), + MovieContract.MovieEntry._ID + " = ?", new String[]{String.valueOf(movie.getId())}); + + } + */ + + + @Override + public List getSavedMovies() { + +// Cursor cursor = mContext.getContentResolver().query(MovieContract.MovieEntry.CONTENT_URI, MOVIE_COLUMNS, null, null, null); + + Cursor cursor = mDatabase.query(MovieContract.MovieEntry.MOVIE_TABLE_NAME, MOVIE_COLUMNS, null, null, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + List movies = new ArrayList<>(cursor.getCount()); + try { + while (!cursor.isAfterLast()) { + movies.add(getMovie(cursor)); + cursor.moveToNext(); + } + } finally { + cursor.close(); + } + return movies; + } + + return Collections.emptyList(); + } + + @Override + public Movie getMovie(Cursor cursor) { + Movie movie = new Movie(null, null, null, null, null, null, null); + movie.setId(cursor.getLong(COL_MOVIE_ID)); + movie.setTitle(cursor.getString(COL_MOVIE_TITLE)); + movie.setRealeaseDate(MovieContract.getDateFromDb(cursor.getString(COL_MOVIE_RELEASE_DATE))); + movie.setOverview(cursor.getString(COL_MOVIE_OVERVIEW)); + movie.setCoverPath(cursor.getString(COL_MOVIE_COVER_PATH)); + movie.setBackdrop(cursor.getString(COL_MOVIE_BACKDROP_PATH)); + movie.setPopularity(cursor.getFloat(COL_MOVIE_POPULARITY)); + + return movie; + } + + + @Override + public boolean containsMovie(Long id) { + Log.d("XXX", BuildConfig.APPLICATION_ID + ".provider"); +// Cursor cursor = mContext.getContentResolver().query(MovieContract.MovieEntry.CONTENT_URI, +// new String[]{MovieContract.MovieEntry._ID}, WHERE_ID, +// new String[]{String.valueOf(id)}, null); + String Query = "Select * from " + MovieContract.MovieEntry.MOVIE_TABLE_NAME + " where " + MovieContract.MovieEntry._ID + " = " + id; + Cursor cursor = mDatabase.rawQuery(Query, null); + if(cursor.getCount() <= 0){ + cursor.close(); + return false; + } + cursor.close(); + return true; + } + + + private ContentValues prepareMovieValues(Movie movie) { + ContentValues values = new ContentValues(); + values.put(MovieContract.MovieEntry._ID, movie.getId()); + values.put(MovieContract.MovieEntry.COLUMN_TITLE, movie.getTitle()); + values.put(MovieContract.MovieEntry.COLUMN_RELEASE_DATE, MovieContract.insertDateToDb(movie.getRealeaseDate())); + values.put(MovieContract.MovieEntry.COLUMN_OVERVIEW, movie.getOverview()); + values.put(MovieContract.MovieEntry.COLUMN_COVER_PATH, movie.getCoverPath()); + values.put(MovieContract.MovieEntry.COLUMN_BACKDROP_PATH, movie.getBackdrop()); + values.put(MovieContract.MovieEntry.COLUMN_POPULARITY, movie.getPopularity()); + + return values; + } + +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieProvider.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieProvider.java new file mode 100644 index 0000000..7ebde8e --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/MovieProvider.java @@ -0,0 +1,155 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.util.Log; +import java.util.Arrays; + + +/** + * Created by BranislavSmik on 12/10/2017. + */ + +public class MovieProvider extends ContentProvider { + + private static final String TAG = MovieProvider.class.getSimpleName(); + + private static final int MOVIE = 100; + private static final int MOVIE_ID = 101; + private static final UriMatcher sUriMatcher = buildUriMatcher(); + private MovieDBHelper mDbHelper; + + private static UriMatcher buildUriMatcher() { + final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); + final String authority = MovieContract.CONTENT_AUTHORITY; + + matcher.addURI(authority, MovieContract.PATH_MOVIES, MOVIE); + matcher.addURI(authority, MovieContract.PATH_MOVIES + "/#", MOVIE_ID); + + return matcher; + } + + @Override + public boolean onCreate() { + mDbHelper = new MovieDBHelper(getContext()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + Log.d(TAG, Arrays.toString(selectionArgs)); + Cursor retCursor; + switch (sUriMatcher.match(uri)) { + case MOVIE_ID: { + retCursor = mDbHelper.getReadableDatabase().query( + MovieContract.MovieEntry.MOVIE_TABLE_NAME, + projection, + MovieContract.MovieEntry._ID + " = '" + ContentUris.parseId(uri) + "'", + null, + null, + null, + sortOrder + ); + break; + } + case MOVIE: { + retCursor = mDbHelper.getReadableDatabase().query( + MovieContract.MovieEntry.MOVIE_TABLE_NAME, + projection, + selection, + selectionArgs, + null, + null, + sortOrder + ); + break; + } + + default: + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + retCursor.setNotificationUri(getContext().getContentResolver(), uri); + return retCursor; + } + + @Override + public String getType(Uri uri) { + final int match = sUriMatcher.match(uri); + + switch (match) { + case MOVIE: + return MovieContract.MovieEntry.CONTENT_TYPE; + case MOVIE_ID: + return MovieContract.MovieEntry.CONTENT_ITEM_TYPE; + default: + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + Log.d(TAG, values.toString()); + + final SQLiteDatabase db = mDbHelper.getWritableDatabase(); + final int match = sUriMatcher.match(uri); + Uri returnUri; + + switch (match) { + case MOVIE: { + long _id = db.insert(MovieContract.MovieEntry.MOVIE_TABLE_NAME, null, values); + if (_id > 0) + returnUri = MovieContract.MovieEntry.buildMovieUri(_id); + else + throw new android.database.SQLException("Failed to insert row into " + uri); + break; + } + default: + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return returnUri; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + final SQLiteDatabase db = mDbHelper.getWritableDatabase(); + final int match = sUriMatcher.match(uri); + int rowsDeleted; + switch (match) { + case MOVIE: + rowsDeleted = db.delete(MovieContract.MovieEntry.MOVIE_TABLE_NAME, selection, selectionArgs); + break; + default: + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + // Because a null deletes all rows + if (selection == null || rowsDeleted != 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return rowsDeleted; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + final SQLiteDatabase db = mDbHelper.getWritableDatabase(); + final int match = sUriMatcher.match(uri); + int rowsUpdated; + + switch (match) { + case MOVIE: + rowsUpdated = db.update(MovieContract.MovieEntry.MOVIE_TABLE_NAME, values, selection, selectionArgs); + break; + default: + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + if (rowsUpdated != 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return rowsUpdated; + } +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Networking.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Networking.java new file mode 100644 index 0000000..5f68d31 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/Networking.java @@ -0,0 +1,70 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +/** + * Created by BranislavSmik on 11/23/2017. + */ + +//THIS CLASS IS OBSOLETE +public class Networking { + + //private static String URL = "http://api.themoviedb.org/"; + //private static String PATH = "3/discover/movie?api_key="; + //private static String QUERY_PARAM_INTHEATRES = "&primary_release_date.gte=2017-12-01&primary_release_date.lte=2017-12-24"; //Just MVP solution + //private static String QUERY_PARAM_DRAMA = "&with_genres=18&sort_by=popularity.desc"; + + private static OkHttpClient client = new OkHttpClient(); + private Networking() {} + + public static ArrayList getMovieList(String query_param) throws IOException { + + Request request = new Request.Builder() + .url(Constants.URL + Constants.PATH + Constants.APIKEYv3 + query_param) + .addHeader("Accept", "application/json") + .addHeader("Content-Type", "application/json") + .build(); + + Call call = client.newCall(request); + Response response = call.execute(); + + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + return getData(response.body().string()); + } + + private static ArrayList getData(String data) { + ArrayList mData = new ArrayList(); + + try { + JSONObject json = new JSONObject(data); + Gson gson = new Gson(); + ArrayList movies = gson.fromJson(json.getJSONArray("results").toString(), new TypeToken>() { + }.getType()); + + for (MovieDTO m : movies) { + Movie movie = new Movie(m.getId() ,m.getRealeaseDateAsLong(), m.getCoverPath(), m.getBackdrop(), m.getTitle(), m.getOverview(), m.getPopularityAsFloat()); + mData.add(movie); + } + } catch (JSONException e) { + e.printStackTrace(); + return new ArrayList(); + } + return mData; + } +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/RecyclerAdapter.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/RecyclerAdapter.java new file mode 100644 index 0000000..23439b6 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/RecyclerAdapter.java @@ -0,0 +1,141 @@ +package cz.muni.fi.pv256.movio2.uco_422678; + +/** + * Created by Branci on 11/2/2017. + */ + + +import android.content.Context; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import java.util.List; +import com.squareup.picasso.Picasso; + +import cz.muni.fi.pv256.movio2.R; + +public class RecyclerAdapter extends RecyclerView.Adapter { + private static final int TYPE_NODATA = 0; + private static final int TYPE_MOVIE = 1; + private static final int TYPE_CATEGORY = 2; + private static final String IMAGE_PATH = "https://image.tmdb.org/t/p/w500/"; + + private Context mContext; + protected static List mMovieList; + + public RecyclerAdapter(Context context, List data) { + mContext = context; + mMovieList = data; + } + + public static class EmptyViewHolder extends RecyclerView.ViewHolder { + public TextView text; + + public EmptyViewHolder(View itemView) { + super(itemView); + text = (TextView) itemView.findViewById(R.id.nodata_textview); + } + } + + public static class CategoryViewHolder extends RecyclerView.ViewHolder { + public TextView text; + + public CategoryViewHolder(View itemView) { + super(itemView); + text = (TextView) itemView.findViewById(R.id.category_name); + } + } + + public static class MovieViewHolder extends RecyclerView.ViewHolder { + public TextView movie_title; + public TextView movie_rating; + public ImageView movie_image; + private RelativeLayout mLayout; + + public MovieViewHolder(View itemView, final Context context) { + super(itemView); + movie_title = (TextView) itemView.findViewById(R.id.movie_title); + movie_image = (ImageView) itemView.findViewById(R.id.movie_image); + movie_rating = (TextView) itemView.findViewById(R.id.movie_rating); + mLayout = (RelativeLayout) itemView.findViewById(R.id.layout); + + View.OnClickListener clickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + if(context != null) { + //((MainActivity) context).onMovieSelect(getAdapterPosition()); + ((MainActivity) context).onMovieSelect((Movie)mMovieList.get(getAdapterPosition())); + } + } + }; + + mLayout.setOnClickListener(clickListener); + + } + } + + // Create new views (invoked by the layout manager) + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.fragment_movie_list, parent, false); + switch(viewType) { + case TYPE_MOVIE: + view = inflater.inflate(R.layout.list_item_movie, parent, false); + return new MovieViewHolder(view, mContext); + case TYPE_CATEGORY: + view = inflater.inflate(R.layout.list_item_category, parent,false); + return new CategoryViewHolder(view); + case TYPE_NODATA: + view = inflater.inflate(R.layout.list_nodata, parent, false); + return new EmptyViewHolder(view); + } + return null; + } + + @Override + public int getItemViewType(int position) { + return mMovieList.get(position) instanceof Movie ? TYPE_MOVIE : TYPE_CATEGORY; + } + + // Replace the contents of a view (invoked by the layout manager) + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + int listItemType = getItemViewType(position); + + switch(listItemType){ + case TYPE_MOVIE: + MovieViewHolder movieHolder = (MovieViewHolder) viewHolder; + Movie movie = (Movie) mMovieList.get(position); + movieHolder.movie_title.setText(movie.getTitle()); + movieHolder.movie_rating.setText(Float.toString(movie.getPopularity())); + Picasso.with(mContext).load(IMAGE_PATH + movie.getCoverPath()).into(movieHolder.movie_image); + break; + case TYPE_CATEGORY: + CategoryViewHolder categoryHolder = (CategoryViewHolder) viewHolder; + categoryHolder.text.setText(mMovieList.get(position).toString()); + break; + case TYPE_NODATA : + EmptyViewHolder emptyView = (EmptyViewHolder) viewHolder; + emptyView.text.setText(mMovieList.get(position).toString()); + break; + } + + } + + public void dataUpdate(List data) { + this.mMovieList = data; + notifyDataSetChanged(); + } + + // Return the size of your dataset (invoked by the layout manager) + @Override + public int getItemCount() { + return mMovieList.size(); + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/MovieSyncBroadcastReceiver.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/MovieSyncBroadcastReceiver.java new file mode 100644 index 0000000..306cb45 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/MovieSyncBroadcastReceiver.java @@ -0,0 +1,16 @@ +package cz.muni.fi.pv256.movio2.uco_422678.sync; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * Created by BranislavSmik on 2/4/2018. + */ + +public class MovieSyncBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + UpdaterSyncAdapter.getSyncAccount(context); + } +} \ No newline at end of file diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/MovieSyncContentProvider.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/MovieSyncContentProvider.java new file mode 100644 index 0000000..6b2e8f2 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/MovieSyncContentProvider.java @@ -0,0 +1,51 @@ +package cz.muni.fi.pv256.movio2.uco_422678.sync; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** + * Created by BranislavSmik on 2/4/2018. + */ + +public class MovieSyncContentProvider extends ContentProvider { + + @Override + public boolean onCreate() { + return true; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + public int update( + Uri uri, + ContentValues values, + String selection, + String[] selectionArgs) { + return 0; + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterAuthenticator.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterAuthenticator.java new file mode 100644 index 0000000..86a141c --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterAuthenticator.java @@ -0,0 +1,80 @@ +package cz.muni.fi.pv256.movio2.uco_422678.sync; + +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.NetworkErrorException; +import android.content.Context; +import android.os.Bundle; + + +/** + * Created by BranislavSmik on 2/4/2018. + */ + +public class UpdaterAuthenticator extends AbstractAccountAuthenticator { + + public UpdaterAuthenticator(Context context) { + super(context); + } + + // No properties to edit. + @Override + public Bundle editProperties( + AccountAuthenticatorResponse r, String s) { + throw new UnsupportedOperationException(); + } + + // We're not actually adding an account to the device - just return null. + @Override + public Bundle addAccount( + AccountAuthenticatorResponse r, + String s, + String s2, + String[] strings, + Bundle bundle) throws NetworkErrorException { + return null; + } + + // Ignore attempts to confirm credentials + @Override + public Bundle confirmCredentials( + AccountAuthenticatorResponse r, + Account account, + Bundle bundle) throws NetworkErrorException { + return null; + } + + // Getting an authentication token is not supported + @Override + public Bundle getAuthToken( + AccountAuthenticatorResponse r, + Account account, + String s, + Bundle bundle) throws NetworkErrorException { + throw new UnsupportedOperationException(); + } + + // Getting a label for the auth token is not supported + @Override + public String getAuthTokenLabel(String s) { + throw new UnsupportedOperationException(); + } + + // Updating user credentials is not supported + @Override + public Bundle updateCredentials( + AccountAuthenticatorResponse r, + Account account, + String s, Bundle bundle) throws NetworkErrorException { + throw new UnsupportedOperationException(); + } + + // Checking features for the account is not supported + @Override + public Bundle hasFeatures( + AccountAuthenticatorResponse r, + Account account, String[] strings) throws NetworkErrorException { + throw new UnsupportedOperationException(); + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterAuthenticatorService.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterAuthenticatorService.java new file mode 100644 index 0000000..83c44b1 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterAuthenticatorService.java @@ -0,0 +1,32 @@ +package cz.muni.fi.pv256.movio2.uco_422678.sync; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * Created by BranislavSmik on 2/4/2018. + */ + +/** + * The service which allows the sync adapter framework to access the authenticator. + */ +public class UpdaterAuthenticatorService extends Service { + // Instance field that stores the authenticator object + private UpdaterAuthenticator mAuthenticator; + + @Override + public void onCreate() { + // Create a new authenticator object + mAuthenticator = new UpdaterAuthenticator(this); + } + + /* + * When the system binds to this Service to make the RPC call + * return the authenticator's IBinder. + */ + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterSyncAdapter.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterSyncAdapter.java new file mode 100644 index 0000000..26f65f3 --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterSyncAdapter.java @@ -0,0 +1,202 @@ +package cz.muni.fi.pv256.movio2.uco_422678.sync; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.content.SyncRequest; +import android.content.SyncResult; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +import java.io.IOException; +import java.util.List; +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import cz.muni.fi.pv256.movio2.uco_422678.Constants; +import cz.muni.fi.pv256.movio2.uco_422678.Movie; +import cz.muni.fi.pv256.movio2.uco_422678.MovieAPI; +import cz.muni.fi.pv256.movio2.uco_422678.MovieDTO; +import cz.muni.fi.pv256.movio2.R; +import cz.muni.fi.pv256.movio2.uco_422678.MovieDBHelper; +import cz.muni.fi.pv256.movio2.uco_422678.MovieManager; +import cz.muni.fi.pv256.movio2.uco_422678.MovieManagerImpl; +import static cz.muni.fi.pv256.movio2.uco_422678.DownloadService.CHANNEL; + +/** + * Created by BranislavSmik on 2/4/2018. + */ + + +public class UpdaterSyncAdapter extends AbstractThreadedSyncAdapter { + + // Interval at which to sync with the server, in seconds. + public static final int SYNC_INTERVAL = 60 * 60 * 24; // a day + public static final int SYNC_FLEXTIME = SYNC_INTERVAL / 3; + + private static final String TAG = UpdaterSyncAdapter.class.getSimpleName(); + + public UpdaterSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + /** + * Helper method to schedule the sync adapter periodic execution + */ + public static void configurePeriodicSync(Context context, int syncInterval, int flexTime) { + Account account = getSyncAccount(context); + String authority = context.getString(R.string.content_authority); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // we can enable inexact timers in our periodic sync + SyncRequest request = new SyncRequest.Builder() + .syncPeriodic(syncInterval, flexTime) + .setSyncAdapter(account, authority) + .setExtras(Bundle.EMPTY) //enter non null Bundle, otherwise on some phones it crashes sync + .build(); + ContentResolver.requestSync(request); + } else { + ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, syncInterval); + } + } + + /** + * Helper method to have the sync adapter sync immediately + * + * @param context The context used to access the account service + */ + public static void syncImmediately(Context context) { + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync(getSyncAccount(context), context.getString(R.string.content_authority), bundle); + } + + public static void initializeSyncAdapter(Context context) { + getSyncAccount(context); + } + + /** + * Helper method to get the fake account to be used with SyncAdapter, or make a new one if the + * fake account doesn't exist yet. If we make a new account, we call the onAccountCreated + * method so we can initialize things. + * + * @param context The context used to access the account service + * @return a fake account. + */ + public static Account getSyncAccount(Context context) { + // Get an instance of the Android account manager + AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); + + // Create the account type and default account + Account newAccount = new Account(context.getString(R.string.app_name), context.getString(R.string.sync_account_type)); + + // If the password doesn't exist, the account doesn't exist + if (null == accountManager.getPassword(newAccount)) { + + /* + * Add the account and account type, no password or user data + * If successful, return the Account object, otherwise report an error. + */ + if (!accountManager.addAccountExplicitly(newAccount, "", null)) { + return null; + } + /* + * If you don't set android:syncable="true" in + * in your element in the manifest, + * then call ContentResolver.setIsSyncable(account, AUTHORITY, 1) + * here. + */ + + onAccountCreated(newAccount, context); + } + return newAccount; + } + + private static void onAccountCreated(Account newAccount, Context context) { + /* + * Since we've created an account + */ + UpdaterSyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME); + + /* + * Without calling setSyncAutomatically, our periodic sync will not be enabled. + */ + ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true); + + /* + * Finally, let's do a sync to get things started + */ + syncImmediately(context); + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { + try { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(Constants.URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + MovieAPI service = retrofit.create(MovieAPI.class); + + MovieDBHelper dbHelper = new MovieDBHelper(getContext()); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + MovieManager movieManager = new MovieManagerImpl(database); + + List savedMovies = movieManager.getSavedMovies(); + for (int i = 0; i < savedMovies.size(); i++) { + Movie movie = savedMovies.get(i); + Call request = service.getMovieById(movie.getId()); + MovieDTO requestMovie = request.execute().body(); + Movie updatedMovie = dtoToMovie(requestMovie); + + Log.d(TAG, "updating movie..."); + if (!compareMovies(movie, updatedMovie)) { + movieManager.deleteMovie(movie); + movieManager.createMovie(updatedMovie); + Notification.Builder n; + if (Build.VERSION.SDK_INT < 26) { + n = new Notification.Builder(getContext()); + } + else { + n = new Notification.Builder(getContext(), CHANNEL); + } + n.setContentTitle("Movio2").setAutoCancel(true) + .setContentText("Movie " + updatedMovie.getTitle() + " updated") + .setSmallIcon(R.mipmap.ic_launcher); + + NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(getContext().NOTIFICATION_SERVICE); + notificationManager.notify(0, n.build()); + } + } + database.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + private Movie dtoToMovie(MovieDTO m){ + Movie movie = new Movie(m.getId(), m.getRealeaseDateAsLong(), m.getCoverPath(), m.getBackdrop(), m.getTitle(), m.getOverview(), m.getPopularityAsFloat()); + + return movie; + } + + private boolean compareMovies(Movie oldMovie, Movie newMovie){ + if (oldMovie.getBackdrop().compareTo(newMovie.getBackdrop()) !=0 || + oldMovie.getOverview().compareTo(newMovie.getOverview()) !=0 || + oldMovie.getTitle().compareTo(newMovie.getTitle()) !=0 || + oldMovie.getCoverPath().compareTo(newMovie.getCoverPath()) !=0 || + Math.abs(oldMovie.getPopularity() - newMovie.getPopularity()) > 0.00001 || + oldMovie.getRealeaseDate() != newMovie.getRealeaseDate()) return false; + else return true; + } + +} diff --git a/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterSyncService.java b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterSyncService.java new file mode 100644 index 0000000..25efe4d --- /dev/null +++ b/app/src/main/java/cz/muni/fi/pv256/movio2/uco_422678/sync/UpdaterSyncService.java @@ -0,0 +1,29 @@ +package cz.muni.fi.pv256.movio2.uco_422678.sync; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * Created by BranislavSmik on 2/4/2018. + */ + +public class UpdaterSyncService extends Service { + + private static final Object LOCK = new Object(); + private static UpdaterSyncAdapter sUpdaterSyncAdapter = null; + + @Override + public void onCreate() { + synchronized (LOCK) { + if (sUpdaterSyncAdapter == null) { + sUpdaterSyncAdapter = new UpdaterSyncAdapter(getApplicationContext(), true); + } + } + } + + @Override + public IBinder onBind(Intent intent) { + return sUpdaterSyncAdapter.getSyncAdapterBinder(); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/minus.png b/app/src/main/res/drawable/minus.png new file mode 100644 index 0000000..7f35a84 Binary files /dev/null and b/app/src/main/res/drawable/minus.png differ diff --git a/app/src/main/res/drawable/plus.png b/app/src/main/res/drawable/plus.png new file mode 100644 index 0000000..5e24f1e Binary files /dev/null and b/app/src/main/res/drawable/plus.png differ diff --git a/app/src/main/res/drawable/sync.png b/app/src/main/res/drawable/sync.png new file mode 100644 index 0000000..dc7e37f Binary files /dev/null and b/app/src/main/res/drawable/sync.png differ diff --git a/app/src/main/res/layout-w600dp/activity_main.xml b/app/src/main/res/layout-w600dp/activity_main.xml new file mode 100644 index 0000000..d6e9bbb --- /dev/null +++ b/app/src/main/res/layout-w600dp/activity_main.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/activity_app.xml b/app/src/main/res/layout/activity_app.xml index 7cd2a6c..9546e0f 100644 --- a/app/src/main/res/layout/activity_app.xml +++ b/app/src/main/res/layout/activity_app.xml @@ -1,4 +1,5 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/activity_detail.xml new file mode 100644 index 0000000..39c40eb --- /dev/null +++ b/app/src/main/res/layout/activity_detail.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..969ad1f --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml new file mode 100644 index 0000000..94246c1 --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml new file mode 100644 index 0000000..860b359 --- /dev/null +++ b/app/src/main/res/layout/fragment_detail.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_movie_list.xml b/app/src/main/res/layout/fragment_movie_list.xml new file mode 100644 index 0000000..fc3ce81 --- /dev/null +++ b/app/src/main/res/layout/fragment_movie_list.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_category.xml b/app/src/main/res/layout/list_item_category.xml new file mode 100644 index 0000000..3908d00 --- /dev/null +++ b/app/src/main/res/layout/list_item_category.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_movie.xml b/app/src/main/res/layout/list_item_movie.xml new file mode 100644 index 0000000..afc3788 --- /dev/null +++ b/app/src/main/res/layout/list_item_movie.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_nodata.xml b/app/src/main/res/layout/list_nodata.xml new file mode 100644 index 0000000..f982c11 --- /dev/null +++ b/app/src/main/res/layout/list_nodata.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/menu_switch.xml b/app/src/main/res/layout/menu_switch.xml new file mode 100644 index 0000000..c605f1f --- /dev/null +++ b/app/src/main/res/layout/menu_switch.xml @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/app/src/main/res/menu/menu.xml b/app/src/main/res/menu/menu.xml new file mode 100644 index 0000000..71ac30f --- /dev/null +++ b/app/src/main/res/menu/menu.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_download.png b/app/src/main/res/mipmap-hdpi/ic_download.png new file mode 100644 index 0000000..1bc425c Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_download.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index cde69bc..3a57790 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 9a078e3..fdd34d3 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_download.png b/app/src/main/res/mipmap-mdpi/ic_download.png new file mode 100644 index 0000000..6450e0d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_download.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index c133a0c..f89436e 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index efc028a..990dfe5 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_download.png b/app/src/main/res/mipmap-xhdpi/ic_download.png new file mode 100644 index 0000000..fc2a540 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_download.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index bfa42f0..5fcc5e3 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 3af2608..99415fe 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_download.png b/app/src/main/res/mipmap-xxhdpi/ic_download.png new file mode 100644 index 0000000..ae62780 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_download.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 324e72c..f4b4f22 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 9bec2e6..4e8dde1 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_download.png b/app/src/main/res/mipmap-xxxhdpi/ic_download.png new file mode 100644 index 0000000..69f880a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_download.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index aee44e1..97f8f05 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 34947cd..fbcd2f6 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..edfb0bd --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,10 @@ + + + メイン + テーマを切り替える + 劇場で + ドラマ + 利用可能な映画はありません + 概要 + + diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000..fe50eff --- /dev/null +++ b/app/src/main/res/values-sk/strings.xml @@ -0,0 +1,10 @@ + + + MainActivity + Prepni tému + V kinách + Dráma + Žiadne dostupné filmy.\n Skontroluj prosím svoje internetové pripojenie. + Prehľad + Dátum vydania: + diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3ab3e9c..32b57ff 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,7 @@ #3F51B5 #303F9F #FF4081 + #ffc107 + #ffa000 + #90a4ae diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..59a0b0c --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,3 @@ + + 16dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1e6a653..1af7235 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,13 @@ - Movio2 + + MainActivity + Switch theme + In theatres now + Drama movies + No movies available.\n Check your internet connection. + Overview + Error parsing data + Release date: + cz.muni.fi.pv256.movio2.uco_422678.provider + cz.muni.fi.pv256.movio2.uco_422678.sync diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5885930..55b9fec 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,11 +1,9 @@ - - - - + + + + + + diff --git a/app/src/main/res/xml/authenticator.xml b/app/src/main/res/xml/authenticator.xml new file mode 100644 index 0000000..6fbe5fa --- /dev/null +++ b/app/src/main/res/xml/authenticator.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/xml/syncadapter.xml b/app/src/main/res/xml/syncadapter.xml new file mode 100644 index 0000000..e986b1e --- /dev/null +++ b/app/src/main/res/xml/syncadapter.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/app/src/secondary/res/values/theme.xml b/app/src/secondary/res/values/theme.xml new file mode 100644 index 0000000..d056dac --- /dev/null +++ b/app/src/secondary/res/values/theme.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/settings.gradle b/settings.gradle index e7b4def..9d495b3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app' \ No newline at end of file