diff --git a/.gitmodules b/.gitmodules index 5fb4a5c..b19ffb1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "libs/privacy-friendly-backup-api"] - path = libs/privacy-friendly-backup-api - url = https://github.com/SecUSo/privacy-friendly-backup-api.git +[submodule "libs/pfa-core"] + path = libs/pfa-core + url = https://github.com/SecUSo/pfa-core.git diff --git a/app/build.gradle b/app/build.gradle index 1fa7e84..a1d4c72 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,9 +63,12 @@ dependencies { annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' implementation 'androidx.activity:activity-ktx:1.9.3' + // pfa-core library + implementation("org.secuso.pfa-core:ui-view") + implementation("org.secuso.pfa-core:model") + // Backup - implementation project(path: ':backup-api') - def work_version = "2.9.1" + def work_version = "2.9.0" implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5fee9ec..b72a5c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,57 +16,14 @@ android:label="@string/app_name" android:supportsRtl="true" android:name=".PF2048" - android:theme="@style/AppTheme"> - - - - - - - - - - + android:theme="@style/PrivacyFriendlyCoreTheme"> - - - - - - + android:theme="@style/PrivacyFriendlyCoreTheme.NoActionBar" /> - - - + android:theme="@style/PrivacyFriendlyCoreTheme.NoActionBar"> @@ -87,17 +44,6 @@ android:value="org.secuso.privacyfriendly2048.activities.MainActivity" /> - - - - - - { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) - } - - "light" -> { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) - } - - else -> { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) - } - } - } - - override val workManagerConfiguration = Configuration.Builder().setMinimumLoggingLevel(Log.INFO).build() +import org.secuso.pfacore.application.BackupDatabaseConfig +import org.secuso.pfacore.application.SQLiteHelperConfig +import org.secuso.pfacore.ui.PFApplication +import org.secuso.pfacore.ui.PFData +import org.secuso.privacyfriendly2048.activities.MainActivity +import org.secuso.privacyfriendly2048.database.PFASQLiteHelper + +class PF2048 : PFApplication() { + override val name: String + get() = ContextCompat.getString(this, R.string.app_name_long) + override val data: PFData + get() = PFApplicationData.instance(this).data + override val mainActivity = MainActivity::class.java + override val database: BackupDatabaseConfig + get() = SQLiteHelperConfig(this, PFASQLiteHelper.DATABASE_NAME) + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder().setMinimumLoggingLevel(Log.INFO).build() } diff --git a/app/src/main/java/org/secuso/privacyfriendly2048/PFApplicationData.kt b/app/src/main/java/org/secuso/privacyfriendly2048/PFApplicationData.kt new file mode 100644 index 0000000..4b2b0b1 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendly2048/PFApplicationData.kt @@ -0,0 +1,162 @@ +package org.secuso.privacyfriendly2048 + +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.lifecycle.map +import org.secuso.pfacore.model.Theme +import org.secuso.pfacore.model.about.About +import org.secuso.pfacore.model.preferences.Preferable +import org.secuso.pfacore.model.preferences.settings.ISettingData +import org.secuso.pfacore.ui.PFData +import org.secuso.pfacore.ui.help.Help +import org.secuso.pfacore.ui.preferences.appPreferences +import org.secuso.pfacore.ui.preferences.settings.appearance +import org.secuso.pfacore.ui.preferences.settings.general +import org.secuso.pfacore.ui.preferences.settings.preferenceFirstTimeLaunch +import org.secuso.pfacore.ui.preferences.settings.settingDeviceInformationOnErrorReport +import org.secuso.pfacore.ui.preferences.settings.settingThemeSelector +import org.secuso.pfacore.ui.tutorial.buildTutorial + +class PFApplicationData private constructor(context: Context) { + + lateinit var theme: ISettingData + private set + lateinit var firstTimeLaunch: Preferable + private set + lateinit var includeDeviceDataInReport: Preferable + private set + lateinit var animationActivated: Preferable + private set + lateinit var currentPage: Preferable + private set + lateinit var prefColorScheme: Preferable + private set + lateinit var prefDisplayLock: Preferable + private set + lateinit var multipleUndo: Preferable + private set + + private val preferences = appPreferences(context) { + preferences { + firstTimeLaunch = preferenceFirstTimeLaunch + currentPage = preference { + key = "currentPage" + default = 0 + backup = true + } + } + settings { + general { + multipleUndo = switch { + key = "multiple_undo" + title { resource(R.string.settings_multiple_undo_title) } + summary { resource(R.string.settings_multiple_undo_summary) } + default = false + backup = true + } + prefDisplayLock = switch { + key = "settings_display" + title { resource(R.string.settings_display) } + summary { literal("") } + default = true + backup = true + } + includeDeviceDataInReport = settingDeviceInformationOnErrorReport + } + appearance { + theme = settingThemeSelector + animationActivated = switch { + key = "pref_animationActivated" + title { resource(R.string.settings_animation_title) } + summary { resource(R.string.settings_animation_summary) } + default = true + backup = true + } + prefColorScheme = radio { + key = "pref_color" + default = "1" + backup = true + entries { + entries(R.array.color_list) + values(resources.getStringArray(R.array.color_list_values).toList()) + } + title { resource(R.string.settings_color) } + summary { transform { state, value -> state.entries.find { it.value == value }!!.entry } } + } + } + } + } + + private val help = Help.build(context) { + listOf( + R.string.help_whatis to R.string.help_whatis_answer, + R.string.help_privacy to R.string.help_privacy_answer, + R.string.help_permission to R.string.help_permission_answer, + R.string.help_play to R.string.help_play_answer, + R.string.help_play_how to R.string.help_play_how_answer, + R.string.help_play_add to R.string.help_play_add_answer, + R.string.help_tip to R.string.help_tip_answer, + R.string.help_undo to R.string.help_undo_answer, + R.string.help_color to R.string.help_color_answer, + ).forEach { (q, a) -> + item { + title { resource(q) } + description { resource(a) } + } + } + + } + + private val about = About( + name = context.resources.getString(R.string.app_name), + version = BuildConfig.VERSION_NAME, + authors = context.resources.getString(R.string.about_author_names), + repo = context.resources.getString(R.string.github) + ) + + private val tutorial = buildTutorial { + stage { + title = ContextCompat.getString(context, R.string.Tutorial_Titel) + images = listOf(R.mipmap.ic_splash) + description = ContextCompat.getString(context, R.string.Tutorial_Instruction) + } + stage { + title = ContextCompat.getString(context, R.string.Tutorial_Titel_Move) + images = if (prefColorScheme.value == "1") { listOf(R.drawable.tutorial_move_s) } else { listOf(R.drawable.tutorial_move_o) } + description = ContextCompat.getString(context, R.string.Tutorial_Move) + } + stage { + title = ContextCompat.getString(context, R.string.Tutorial_Titel_Move) + images = if (prefColorScheme.value == "1") { listOf(R.drawable.tutorial_swipe_s) } else { listOf(R.drawable.tutorial_swipe_o) } + description = ContextCompat.getString(context, R.string.Tutorial_Move) + } + stage { + title = ContextCompat.getString(context, R.string.Tutorial_Titel_Add) + images = if (prefColorScheme.value == "1") { listOf(R.drawable.tutorial_add_s) } else { listOf(R.drawable.tutorial_add_o) } + description = ContextCompat.getString(context, R.string.Tutorial_Add) + } + } + + val data = PFData( + about = about, + help = help, + settings = preferences.settings, + tutorial = tutorial, + theme = theme.state.map { Theme.valueOf(it) }, + firstLaunch = firstTimeLaunch, + includeDeviceDataInReport = includeDeviceDataInReport, + ) + + companion object { + private var _instance: PFApplicationData? = null + fun instance(context: Context): PFApplicationData { + if (_instance == null) { + _instance = PFApplicationData(context) + } + return _instance!! + } + + } +} + + diff --git a/app/src/main/java/org/secuso/privacyfriendly2048/activities/AboutActivity.java b/app/src/main/java/org/secuso/privacyfriendly2048/activities/AboutActivity.java deleted file mode 100644 index ff18451..0000000 --- a/app/src/main/java/org/secuso/privacyfriendly2048/activities/AboutActivity.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - This file is part of Privacy Friendly 2048. This app implements the functions of the - game 2048 in a privacy friendly version. - - Privacy Friendly 2048 is free software: - you can redistribute it and/or modify it under the terms of the - GNU General Public License as published by the Free Software Foundation, - either version 3 of the License, or any later version. - - Privacy Friendly App Example is distributed in the hope - that it will be useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Privacy Friendly App Example. If not, see . - */ - -package org.secuso.privacyfriendly2048.activities; - -import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.view.View; -import android.widget.TextView; - -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; - -import org.secuso.privacyfriendly2048.BuildConfig; -import org.secuso.privacyfriendly2048.R; -import org.secuso.privacyfriendly2048.activities.helper.BaseActivity; - -/** - * This activity shows the important information about the app. - * Information like the developers, the version and the superior project for which this app was implemented, are presented in this activity. - * - * @author Julian Wadephul and Saskia Jacob - * @version 20180910 - */ -public class AboutActivity extends AppCompatActivity { - - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_about); - - ActionBar ab = getSupportActionBar(); - if (ab != null) { - ab.setDisplayHomeAsUpEnabled(true); - } - - View mainContent = findViewById(R.id.main_content); - if (mainContent != null) { - mainContent.setAlpha(0); - mainContent.animate().alpha(1).setDuration(BaseActivity.MAIN_CONTENT_FADEIN_DURATION); - } - - overridePendingTransition(0, 0); - - ((TextView) findViewById(R.id.secusoWebsite)).setMovementMethod(LinkMovementMethod.getInstance()); - ((TextView) findViewById(R.id.githubURL)).setMovementMethod(LinkMovementMethod.getInstance()); - ((TextView) findViewById(R.id.textFieldVersionName)).setText(getString(R.string.version_number, BuildConfig.VERSION_NAME)); - } - - //@Override - //protected int getNavigationDrawerID() { - // return R.id.nav_about; - //} -} - diff --git a/app/src/main/java/org/secuso/privacyfriendly2048/activities/Element.java b/app/src/main/java/org/secuso/privacyfriendly2048/activities/Element.java deleted file mode 100644 index 56a8a1e..0000000 --- a/app/src/main/java/org/secuso/privacyfriendly2048/activities/Element.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - This file is part of Privacy Friendly 2048. This app implements the functions of the - game 2048 in a privacy friendly version. - - Privacy Friendly 2048 is free software: - you can redistribute it and/or modify it under the terms of the - GNU General Public License as published by the Free Software Foundation, - either version 3 of the License, or any later version. - - Privacy Friendly App Example is distributed in the hope - that it will be useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Privacy Friendly App Example. If not, see . - */ - - -package org.secuso.privacyfriendly2048.activities; - -import static org.secuso.privacyfriendly2048.helpers.ThemeResolverKt.GetColorInt; -import static org.secuso.privacyfriendly2048.helpers.ThemeResolverKt.GetColorRes; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.util.TypedValue; -import android.view.View; - -import org.secuso.privacyfriendly2048.R; - -/** - * This class extends the android.support.v7.widget.AppCompatButton class and represents a box on the game field. - * This element contains different features like the presented value (number) and the position of this value in the playing field (posX, posY). - * Because of the animation a distinction is made between the calculated value and the displayed value. - * Furthermore there are more features like the color of the box and the font size of the value. - * - * @author Julian Wadephul and Saskia Jacob - * @version 20180910 - */ - -public class Element extends androidx.appcompat.widget.AppCompatButton { - public int number = 0; - public int dNumber = 0; - public int posX = 0; - public int posY = 0; - public int dPosX = 0; - public int dPosY = 0; - public boolean activated; - public boolean animateMoving = false; - Context context; - int color; - - - @SuppressLint("RestrictedApi") - public Element(Context c) { - super(c); - context = c; - setAllCaps(false); - setBackgroundResource(R.drawable.game_brick); - setColor(GetColorRes(context, R.attr.buttonEmpty)); - setMaxLines(1); - setAutoSizeTextTypeUniformWithConfiguration(1, - 100, - 1, - TypedValue.COMPLEX_UNIT_SP); - } - - public void drawItem() { - dNumber = number; - activated = (number != 0); - if (number == 0) { - setVisibility(View.INVISIBLE); - setText(""); - } else { - setText("" + number); - if (getVisibility() != View.VISIBLE) - setVisibility(View.VISIBLE); - } - - switch (number) { - case 0: - setColor(GetColorRes(context, R.attr.buttonEmpty)); - setTextColor(GetColorInt(context, R.attr.buttonEmptyText)); - break; - case 2: - setColor(GetColorRes(context, R.attr.button2)); - setTextColor(GetColorInt(context, R.attr.button2Text)); - break; - case 4: - setColor(GetColorRes(context, R.attr.button4)); - setTextColor(GetColorInt(context, R.attr.button4Text)); - break; - case 8: - setColor(GetColorRes(context, R.attr.button8)); - setTextColor(GetColorInt(context, R.attr.button8Text)); - break; - case 16: - setColor(GetColorRes(context, R.attr.button16)); - setTextColor(GetColorInt(context, R.attr.button16Text)); - break; - case 32: - setColor(GetColorRes(context, R.attr.button32)); - setTextColor(GetColorInt(context, R.attr.button32Text)); - break; - case 64: - setColor(GetColorRes(context, R.attr.button64)); - setTextColor(GetColorInt(context, R.attr.button64Text)); - break; - case 128: - setColor(GetColorRes(context, R.attr.button128)); - setTextColor(GetColorInt(context, R.attr.button128Text)); - break; - case 256: - setColor(GetColorRes(context, R.attr.button256)); - setTextColor(GetColorInt(context, R.attr.button256Text)); - break; - case 512: - setColor(GetColorRes(context, R.attr.button512)); - setTextColor(GetColorInt(context, R.attr.button512Text)); - break; - case 1024: - setColor(GetColorRes(context, R.attr.button1024)); - setTextColor(GetColorInt(context, R.attr.button1024Text)); - break; - case 2048: - setColor(GetColorRes(context, R.attr.button2048)); - setTextColor(GetColorInt(context, R.attr.button2048Text)); - break; - case 4096: - setColor(GetColorRes(context, R.attr.button4096)); - setTextColor(GetColorInt(context, R.attr.button4096Text)); - break; - case 8192: - setColor(GetColorRes(context, R.attr.button8192)); - setTextColor(GetColorInt(context, R.attr.button8192Text)); - break; - case 16384: - setColor(GetColorRes(context, R.attr.button16384)); - setTextColor(GetColorInt(context, R.attr.button16384Text)); - break; - case 32768: - setColor(GetColorRes(context, R.attr.button32768)); - setTextColor(GetColorInt(context, R.attr.button32768Text)); - break; - } - } - - private void setColor(int c) { - color = c; - Drawable background = getBackground(); - if (background instanceof ShapeDrawable) { - ((ShapeDrawable) background).getPaint().setColor(c); - } else if (background instanceof GradientDrawable) { - ((GradientDrawable) background).setColor(c); - } else if (background instanceof ColorDrawable) { - ((ColorDrawable) background).setColor(c); - } - } - - public String toString() { - return "number: " + number; - } - - public int getNumber() { - return number; - } - - public void setDPosition(int i, int j) { - dPosX = i; - dPosY = j; - } - - public void setNumber(int i) { - number = i; - } - - public int getdPosX() { - return dPosX; - } - - public int getdPosY() { - return dPosY; - } - - public int getdNumber() { - return dNumber; - } - - public int getPosX() { - return posX; - } - - public int getPosY() { - return posY; - } - - public Element copy() { - Element temp = new Element(context); - temp.number = number; - temp.dNumber = dNumber; - temp.posX = posX; - temp.posY = posY; - temp.dPosX = dPosX; - temp.dPosY = dPosY; - temp.activated = activated; - temp.animateMoving = animateMoving; - temp.color = color; - //temp.setBackgroundResource(backGroundResource); - temp.setColor(color); - temp.setVisibility(getVisibility()); - temp.setLayoutParams(getLayoutParams()); - return temp; - } -} diff --git a/app/src/main/java/org/secuso/privacyfriendly2048/activities/GameActivity.kt b/app/src/main/java/org/secuso/privacyfriendly2048/activities/GameActivity.kt index 61c710d..83be9c6 100644 --- a/app/src/main/java/org/secuso/privacyfriendly2048/activities/GameActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendly2048/activities/GameActivity.kt @@ -21,22 +21,22 @@ import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Intent import android.os.Bundle -import android.preference.PreferenceManager import android.util.Log +import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.WindowManager -import android.widget.GridView import android.widget.ImageButton import android.widget.TextView import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.SimpleItemAnimator +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.secuso.privacyfriendly2048.PFApplicationData import org.secuso.privacyfriendly2048.R import org.secuso.privacyfriendly2048.activities.adapter.Grid2048Adapter import org.secuso.privacyfriendly2048.activities.adapter.Grid2048BackgroundAdapter -import org.secuso.privacyfriendly2048.activities.helper.BaseActivityWithoutNavBar import org.secuso.privacyfriendly2048.activities.helper.Gestures import org.secuso.privacyfriendly2048.activities.helper.GridRecyclerView import org.secuso.privacyfriendly2048.activities.viewmodel.GameViewModel @@ -44,8 +44,6 @@ import org.secuso.privacyfriendly2048.model.Direction import org.secuso.privacyfriendly2048.model.Game2048 import org.secuso.privacyfriendly2048.model.GameBoard import org.secuso.privacyfriendly2048.model.GameState -import kotlin.math.abs -import kotlin.math.max /** * This activity contains the entire game and draws the game field depending on the selected mode and the screen size. @@ -63,7 +61,7 @@ import kotlin.math.max * @author Patrick Schneider * @version 20241107 */ -class GameActivity: BaseActivityWithoutNavBar() { +class GameActivity: org.secuso.pfacore.ui.activities.BaseActivity() { val viewModel: GameViewModel by viewModels { GameViewModel.GameViewModelFactory( filesDir, Game2048.GameConfig( @@ -80,13 +78,12 @@ class GameActivity: BaseActivityWithoutNavBar() { val undoButton by lazy { findViewById(R.id.undoButton) } val grid by lazy { findViewById(R.id.grid) } val gridBackground by lazy { findViewById(R.id.grid_background) } - val adapter by lazy { Grid2048Adapter(this, layoutInflater, viewModel.board()) } + val adapter by lazy { Grid2048Adapter(this, layoutInflater, grid, viewModel.board()) { animation_running = false } } val adapterBackground by lazy { Grid2048BackgroundAdapter(viewModel.boardSize, layoutInflater) } var gameWon = false - - val sharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) } - val animationActivated by lazy { sharedPreferences.getBoolean("pref_animationActivated", true) } + var already_undone = false + var animation_running = false val gestureListener by lazy { object : Gestures(this@GameActivity) { @@ -108,7 +105,7 @@ class GameActivity: BaseActivityWithoutNavBar() { setContentView(R.layout.activity_game); super.onCreate(savedInstanceState); - if (sharedPreferences.getBoolean("settings_display", true)) { + if (PFApplicationData.instance(this).prefDisplayLock.value) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } @@ -117,15 +114,14 @@ class GameActivity: BaseActivityWithoutNavBar() { findViewById(R.id.restartButton).setOnClickListener { viewModel.stop() viewModel.reset() + viewModel.start() + lifecycleScope.launch { + adapter.updateGrid(viewModel.board(), listOf()) + @SuppressLint("NotifyDataSetChanged") + adapter.notifyDataSetChanged() + } } - undoButton.setOnClickListener { - Log.d("GameActivity", "undo pressed") - viewModel.undo() - adapter.updateGrid(viewModel.board(), listOf()) - @SuppressLint("NotifyDataSetChanged") - adapter.notifyDataSetChanged() - undoButton.visibility = if (viewModel.canUndo()) { View.VISIBLE } else { View.INVISIBLE } - } + undoButton.setOnClickListener { undo() } undoButton.visibility = if (viewModel.canUndo()) { View.VISIBLE } else { View.INVISIBLE } viewModel.start() @@ -137,7 +133,7 @@ class GameActivity: BaseActivityWithoutNavBar() { grid.adapter = adapter grid.setHasFixedSize(true) - if (!animationActivated) { + if (!PFApplicationData.instance(this).animationActivated.value) { grid.itemAnimator = null } @@ -149,8 +145,32 @@ class GameActivity: BaseActivityWithoutNavBar() { gridBackground.setHasFixedSize(true) } + private fun undo() { + Log.d("GameActivity", "undo pressed") + viewModel.undo() + already_undone = true + lifecycleScope.launch { + adapter.updateGrid(viewModel.board(), listOf()) + } + @SuppressLint("NotifyDataSetChanged") + adapter.notifyDataSetChanged() + undoButton.visibility = if (viewModel.canUndo() && (PFApplicationData.instance(this).multipleUndo.value || !already_undone)) { View.VISIBLE } else { View.INVISIBLE } + } + private fun move(direction: Direction): Boolean { - adapter.updateGrid(viewModel.board(), viewModel.move(direction)) + if (animation_running) { + return false + } + lifecycleScope.launch { + viewModel.move(direction).let { events -> + if (events.isNotEmpty()) { + animation_running = true + Log.d("animation", "started") + adapter.updateGrid(viewModel.board(), events) + already_undone = false + } + } + } textPoints.text = viewModel.points.toString() textRecord.text = viewModel.stats.record.toString() undoButton.visibility = if (viewModel.canUndo()) { View.VISIBLE } else { View.INVISIBLE } @@ -186,6 +206,32 @@ class GameActivity: BaseActivityWithoutNavBar() { return false } + // This handles actions via a S-Pen + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + when (keyCode) { + KeyEvent.KEYCODE_DPAD_UP -> { + move(Direction.UP) + return true + } + KeyEvent.KEYCODE_DPAD_DOWN -> { + move(Direction.DOWN) + return true + } + KeyEvent.KEYCODE_DPAD_LEFT -> { + move(Direction.LEFT) + return true + } + KeyEvent.KEYCODE_DPAD_RIGHT -> { + move(Direction.RIGHT) + return true + } + KeyEvent.KEYCODE_Z -> if (event?.isCtrlPressed == true) { + undo() + } + } + return super.onKeyDown(keyCode, event) + } + override fun onPause() { viewModel.save() super.onPause() diff --git a/app/src/main/java/org/secuso/privacyfriendly2048/activities/HelpActivity.java b/app/src/main/java/org/secuso/privacyfriendly2048/activities/HelpActivity.java deleted file mode 100644 index 2811e0e..0000000 --- a/app/src/main/java/org/secuso/privacyfriendly2048/activities/HelpActivity.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - This file is part of Privacy Friendly 2048. This app implements the functions of the - game 2048 in a privacy friendly version. - - Privacy Friendly 2048 is free software: - you can redistribute it and/or modify it under the terms of the - GNU General Public License as published by the Free Software Foundation, - either version 3 of the License, or any later version. - - Privacy Friendly App Example is distributed in the hope - that it will be useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Privacy Friendly App Example. If not, see . - */ - -package org.secuso.privacyfriendly2048.activities; - -import android.os.Bundle; -import android.widget.ExpandableListView; - -import org.secuso.privacyfriendly2048.R; -import org.secuso.privacyfriendly2048.activities.adapter.HelpExpandableListAdapter; -import org.secuso.privacyfriendly2048.activities.helper.BaseActivity; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; - -/** - * The HelpActivity is a standard activity provided by all SECUSO apps. Here you can find some FAQs with an adequate answer. - * - * @author Julian Wadephul and Saskia Jacob - * @version 20180910 - */ -public class HelpActivity extends BaseActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_help); - - LinkedHashMap> expandableListDetail = buildData(); - - ExpandableListView generalExpandableListView = findViewById(R.id.generalExpandableListView); - generalExpandableListView.setAdapter(new HelpExpandableListAdapter(this, new ArrayList<>(expandableListDetail.keySet()), expandableListDetail)); - - overridePendingTransition(0, 0); - } - - private LinkedHashMap> buildData() { - LinkedHashMap> expandableListDetail = new LinkedHashMap>(); - - expandableListDetail.put(getString(R.string.help_whatis), Collections.singletonList(getString(R.string.help_whatis_answer))); - expandableListDetail.put(getString(R.string.help_privacy), Collections.singletonList(getString(R.string.help_privacy_answer))); - expandableListDetail.put(getString(R.string.help_permission), Collections.singletonList(getString(R.string.help_permission_answer))); - expandableListDetail.put(getString(R.string.help_play), Collections.singletonList(getString(R.string.help_play_answer))); - expandableListDetail.put(getString(R.string.help_play_how), Collections.singletonList(getString(R.string.help_play_how_answer))); - expandableListDetail.put(getString(R.string.help_play_add), Collections.singletonList(getString(R.string.help_play_add_answer))); - expandableListDetail.put(getString(R.string.help_tip), Collections.singletonList(getString(R.string.help_tip_answer))); - expandableListDetail.put(getString(R.string.help_undo), Collections.singletonList(getString(R.string.help_undo_answer))); - expandableListDetail.put(getString(R.string.help_color), Collections.singletonList(getString(R.string.help_color_answer))); - - - return expandableListDetail; - } - - protected int getNavigationDrawerID() { - return R.id.nav_help; - } - -} diff --git a/app/src/main/java/org/secuso/privacyfriendly2048/activities/MainActivity.java b/app/src/main/java/org/secuso/privacyfriendly2048/activities/MainActivity.java deleted file mode 100644 index d42fc98..0000000 --- a/app/src/main/java/org/secuso/privacyfriendly2048/activities/MainActivity.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - This file is part of Privacy Friendly 2048. This app implements the functions of the - game 2048 in a privacy friendly version. - - Privacy Friendly 2048 is free software: - you can redistribute it and/or modify it under the terms of the - GNU General Public License as published by the Free Software Foundation, - either version 3 of the License, or any later version. - - Privacy Friendly App Example is distributed in the hope - that it will be useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Privacy Friendly App Example. If not, see . - */ - -package org.secuso.privacyfriendly2048.activities; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Html; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.core.app.TaskStackBuilder; -import androidx.core.content.ContextCompat; -import androidx.viewpager.widget.PagerAdapter; -import androidx.viewpager.widget.ViewPager; - -import com.bumptech.glide.Glide; - -import org.secuso.privacyfriendly2048.R; -import org.secuso.privacyfriendly2048.activities.helper.BaseActivity; -import org.secuso.privacyfriendly2048.helpers.FirstLaunchManager; - -import java.io.File; - -/** - * The MainActivity is the activity from which the game in each mode is started. - * Therefore a ViewPager is used to depict pictures of the four mode. - * At the bottom of the activity there are two buttons, which start a new game (START NEW GAME) and continue the previous game (CONTINUE GAME). - * The CONTINUE GAME button is greyed out and is not selectable, if there is no previous played game available in this mode. - * - * @author Julian Wadephul and Saskia Jacob - * @version 20180910 - */ -public class MainActivity extends BaseActivity { - - private ViewPager viewPager; - private MainActivity.MyViewPagerAdapter myViewPagerAdapter; - private LinearLayout dotsLayout; - private TextView[] dots; - private ImageButton btnPrev, btnNext; - private FirstLaunchManager firstLaunchManager; - private int currentPage = 0; - private SharedPreferences.Editor editor; - private SharedPreferences preferences; - private String mypref = "myPref"; - - private int[] layouts = new int[]{ - R.layout.choose_slide1, - R.layout.choose_slide2, - R.layout.choose_slide3, - R.layout.choose_slide4, - }; - private boolean[] gameResumeable = new boolean[]{ - false, - false, - false, - false - }; - - @Override - protected void onStart() { - super.onStart(); - preferences = getApplicationContext().getSharedPreferences(mypref, Context.MODE_PRIVATE); - editor = preferences.edit(); - currentPage = preferences.getInt("currentPage", 0); - viewPager.setCurrentItem(currentPage); - updateButtons(currentPage); - updateMovingButtons(currentPage); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - - overridePendingTransition(0, 0); - - // Making notification bar transparent - if (Build.VERSION.SDK_INT >= 21) { - getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - - firstLaunchManager = new FirstLaunchManager(this); - - viewPager = (ViewPager) findViewById(R.id.view_pager); - dotsLayout = (LinearLayout) findViewById(R.id.layoutDots); - btnPrev = (ImageButton) findViewById(R.id.btn_prev); - btnNext = (ImageButton) findViewById(R.id.btn_next); - - - //checking resumable - File directory = getFilesDir(); - File[] files = directory.listFiles(); - - for (int i = 0; i < files.length; i++) { - Log.i("files", files[i].getName()); - for (int j = 0; j < gameResumeable.length; j++) { - if (files[i].getName().equals("state" + (j + 4) + ".txt")) - gameResumeable[j] = true; - } - } - - - // adding bottom dots - addBottomDots(0); - - // making notification bar transparent - changeStatusBarColor(); - - myViewPagerAdapter = new MainActivity.MyViewPagerAdapter(); - - viewPager.setAdapter(myViewPagerAdapter); - viewPager.addOnPageChangeListener(viewPagerPageChangeListener); - - btnPrev.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - int current = getItem(-1); - if (current >= 0) { - // move to next screen - viewPager.setCurrentItem(current); - } else { - } - } - }); - - btnNext.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - // checking for last page - // if last page home screen will be launched - int current = getItem(+1); - if (current < layouts.length) { - // move to next screen - viewPager.setCurrentItem(current); - } else { - } - } - }); - } - - private void addListener(Button b1, Button b2, int n) { - final int temp = n; - b1.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, GameActivity.class); - intent.putExtra("n", temp); - intent.putExtra("points", 0); - intent.putExtra("new", true); - intent.putExtra("filename", "state" + temp + ".txt"); - intent.putExtra("undo", false); - createBackStack(intent); - } - }); - b2.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, GameActivity.class); - intent.putExtra("n", temp); - intent.putExtra("new", false); - intent.putExtra("filename", "state" + temp + ".txt"); - intent.putExtra("undo", false); - createBackStack(intent); - } - }); - } - - private void createBackStack(Intent intent) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - TaskStackBuilder builder = TaskStackBuilder.create(this); - builder.addNextIntentWithParentStack(intent); - builder.startActivities(); - } else { - startActivity(intent); - finish(); - } - } - - private void addBottomDots(int currentPage) { - dots = new TextView[layouts.length]; - - int activeColor = ContextCompat.getColor(this, R.color.dot_light_screen); - int inactiveColor = ContextCompat.getColor(this, R.color.dot_dark_screen); - - dotsLayout.removeAllViews(); - for (int i = 0; i < dots.length; i++) { - dots[i] = new TextView(this); - dots[i].setText(Html.fromHtml("•")); - dots[i].setTextSize(35); - dots[i].setTextColor(inactiveColor); - dotsLayout.addView(dots[i]); - } - - if (dots.length > 0) - dots[currentPage].setTextColor(activeColor); - } - - private int getItem(int i) { - return viewPager.getCurrentItem() + i; - } - - // viewpager change listener - ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() { - - @Override - public void onPageSelected(int position) { - addBottomDots(position); - currentPage = position; - editor.putInt("currentPage", currentPage); - editor.commit(); - updateButtons(position); - - updateMovingButtons(position); - } - - @Override - public void onPageScrolled(int arg0, float arg1, int arg2) { - - } - - @Override - public void onPageScrollStateChanged(int arg0) { - - } - }; - - public void updateMovingButtons(int position) { - if (position == layouts.length - 1) { - // last page. make button text to GOT IT - btnNext.setVisibility(View.INVISIBLE); - } else { - // still pages are left - btnNext.setVisibility(View.VISIBLE); - } - if (position == 0) { - // last page. make button text to GOT IT - btnPrev.setVisibility(View.INVISIBLE); - } else { - // still pages are left - btnPrev.setVisibility(View.VISIBLE); - } - } - - public void updateButtons(int position) { - Button newGameButton = MainActivity.this.findViewById(R.id.button_newGame); - Button continueButton = MainActivity.this.findViewById(R.id.button_continueGame); - try { - if (gameResumeable[position]) - continueButton.setBackgroundResource(R.drawable.standalone_button); - else - continueButton.setBackgroundResource(R.drawable.inactive_button); - - continueButton.setEnabled(gameResumeable[position]); - } catch (Exception aie) { - aie.printStackTrace(); - } - addListener(newGameButton, continueButton, position + 4); - } - - /** - * Making notification bar transparent - */ - private void changeStatusBarColor() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.TRANSPARENT); - } - } - - /** - * View pager adapter - */ - public class MyViewPagerAdapter extends PagerAdapter { - private LayoutInflater layoutInflater; - - public MyViewPagerAdapter() { - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = layoutInflater.inflate(layouts[position], container, false); - container.addView(view); - ImageView imageView; - switch (position) { - case 0: - imageView = (ImageView) findViewById(R.id.main_menu_img1); - if (PreferenceManager.getDefaultSharedPreferences(MainActivity.this).getString("pref_color", "1").equals("1")) - Glide.with(MainActivity.this).load(R.drawable.layout4x4_s).into(imageView); - else - Glide.with(MainActivity.this).load(R.drawable.layout4x4_o).into(imageView); - break; - case 1: - imageView = (ImageView) findViewById(R.id.main_menu_img2); - if (PreferenceManager.getDefaultSharedPreferences(MainActivity.this).getString("pref_color", "1").equals("1")) - Glide.with(MainActivity.this).load(R.drawable.layout5x5_s).into(imageView); - else - Glide.with(MainActivity.this).load(R.drawable.layout5x5_o).into(imageView); - break; - case 2: - imageView = (ImageView) findViewById(R.id.main_menu_img3); - if (PreferenceManager.getDefaultSharedPreferences(MainActivity.this).getString("pref_color", "1").equals("1")) - Glide.with(MainActivity.this).load(R.drawable.layout6x6_s).into(imageView); - else - Glide.with(MainActivity.this).load(R.drawable.layout6x6_o).into(imageView); - break; - case 3: - imageView = (ImageView) findViewById(R.id.main_menu_img4); - if (PreferenceManager.getDefaultSharedPreferences(MainActivity.this).getString("pref_color", "1").equals("1")) - Glide.with(MainActivity.this).load(R.drawable.layout7x7_s).into(imageView); - else - Glide.with(MainActivity.this).load(R.drawable.layout7x7_o).into(imageView); - break; - } - return view; - } - - @Override - public int getCount() { - return layouts.length; - } - - @Override - public boolean isViewFromObject(View view, Object obj) { - return view == obj; - } - - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - View view = (View) object; - container.removeView(view); - } - } - - /** - * This method connects the Activity to the menu item - * - * @return ID of the menu item it belongs to - */ - @Override - protected int getNavigationDrawerID() { - return R.id.nav_example; - } - -} diff --git a/app/src/main/java/org/secuso/privacyfriendly2048/activities/MainActivity.kt b/app/src/main/java/org/secuso/privacyfriendly2048/activities/MainActivity.kt new file mode 100644 index 0000000..d80dd86 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendly2048/activities/MainActivity.kt @@ -0,0 +1,261 @@ +/* + This file is part of Privacy Friendly 2048. This app implements the functions of the + game 2048 in a privacy friendly version. + + Privacy Friendly 2048 is free software: + you can redistribute it and/or modify it under the terms of the + GNU General Public License as published by the Free Software Foundation, + either version 3 of the License, or any later version. + + Privacy Friendly App Example is distributed in the hope + that it will be useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Privacy Friendly App Example. If not, see . + */ +package org.secuso.privacyfriendly2048.activities + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.text.HtmlCompat +import androidx.viewpager.widget.PagerAdapter +import androidx.viewpager.widget.ViewPager +import androidx.viewpager.widget.ViewPager.OnPageChangeListener +import com.bumptech.glide.Glide +import org.secuso.pfacore.model.DrawerElement +import org.secuso.privacyfriendly2048.PFApplicationData +import org.secuso.privacyfriendly2048.R +import org.secuso.privacyfriendly2048.activities.helper.BaseActivity + +/** + * The MainActivity is the activity from which the game in each mode is started. + * Therefore a ViewPager is used to depict pictures of the four mode. + * At the bottom of the activity there are two buttons, which start a new game (START NEW GAME) and continue the previous game (CONTINUE GAME). + * The CONTINUE GAME button is greyed out and is not selectable, if there is no previous played game available in this mode. + * + * @author Julian Wadephul and Saskia Jacob + * @version 20180910 + */ +class MainActivity : BaseActivity() { + private val viewPager: ViewPager by lazy { findViewById(R.id.view_pager) } + private val dotsLayout: LinearLayout by lazy { findViewById(R.id.layoutDots) } + private lateinit var dots: Array + private val btnPrev: ImageButton by lazy { findViewById(R.id.btn_prev) } + private val btnNext: ImageButton by lazy { findViewById(R.id.btn_next) } + private val currentPage by lazy { PFApplicationData.instance(this).currentPage } + private val myViewPagerAdapter: MyViewPagerAdapter by lazy { MyViewPagerAdapter() } + + private val layouts = intArrayOf( + R.layout.choose_slide1, + R.layout.choose_slide2, + R.layout.choose_slide3, + R.layout.choose_slide4, + ) + private val gameResumeable = booleanArrayOf( + false, + false, + false, + false + ) + + override fun isActiveDrawerElement(element: DrawerElement) = element.name == ContextCompat.getString(this, R.string.action_main) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // adding bottom dots + addBottomDots(currentPage.value) + currentPage.state.observe(this) { + addBottomDots(it) + updateButtons(it) + updateMovingButtons(it) + } + + //checking resumable + val directory = filesDir + val files = directory.listFiles() + + for (i in files!!.indices) { + Log.i("files", files[i].name) + for (j in gameResumeable.indices) { + if (files[i].name == "state" + (j + 4) + ".txt") gameResumeable[j] = true + } + } + + viewPager.adapter = myViewPagerAdapter + viewPager.currentItem = currentPage.value + viewPager.addOnPageChangeListener(object : OnPageChangeListener { + override fun onPageSelected(position: Int) { + currentPage.value = position + } + + override fun onPageScrolled(arg0: Int, arg1: Float, arg2: Int) { + } + + override fun onPageScrollStateChanged(arg0: Int) { + } + } + ) + + btnPrev.setOnClickListener { + getItem(-1).let { + if (it >= 0) { + viewPager.currentItem = it + } + } + } + + btnNext.setOnClickListener { + // checking for last page + // if last page home screen will be launched + getItem(+1).let { + if (it < layouts.size) { + viewPager.currentItem = it + } + } + } + } + + private fun addListener(b1: Button, b2: Button, n: Int) { + b1.setOnClickListener { + val intent = Intent(this@MainActivity, GameActivity::class.java) + intent.putExtra("n", n) + intent.putExtra("points", 0) + intent.putExtra("new", true) + intent.putExtra("filename", "state$n.txt") + intent.putExtra("undo", false) + startActivity(intent) + } + b2.setOnClickListener { + val intent = Intent(this@MainActivity, GameActivity::class.java) + intent.putExtra("n", n) + intent.putExtra("new", false) + intent.putExtra("filename", "state$n.txt") + intent.putExtra("undo", false) + startActivity(intent) + } + } + + private fun addBottomDots(currentPage: Int) { + dots = arrayOfNulls(layouts.size) + + val activeColor = ContextCompat.getColor(this, R.color.dot_light_screen) + val inactiveColor = ContextCompat.getColor(this, R.color.dot_dark_screen) + + dotsLayout.removeAllViews() + for (i in dots.indices) { + dots[i] = TextView(this) + dots[i]!!.text = HtmlCompat.fromHtml("•", HtmlCompat.FROM_HTML_MODE_LEGACY) + dots[i]!!.textSize = 35f + dots[i]!!.setTextColor(inactiveColor) + dotsLayout.addView(dots[i]) + } + + if (dots.isNotEmpty()) dots[currentPage]!!.setTextColor(activeColor) + } + + private fun getItem(i: Int): Int { + return viewPager.currentItem + i + } + + + + fun updateMovingButtons(position: Int) { + if (position == layouts.size - 1) { + btnNext.visibility = View.INVISIBLE + } else { + btnNext.visibility = View.VISIBLE + } + if (position == 0) { + btnPrev.visibility = View.INVISIBLE + } else { + btnPrev.visibility = View.VISIBLE + } + } + + fun updateButtons(position: Int) { + val newGameButton = this@MainActivity.findViewById